Compare commits

..

291 commits

Author SHA1 Message Date
SleeplessOne1917
8ba9bd22e0
Allow Arabic and Cyrillic characters when signing up or creating community (#2780)
* Allow Arabic and Cyrillic characters when signing up or creating community

* Fix mistake with escape

* Another escape mistake
2024-10-29 09:15:21 -04:00
renovate[bot]
05362c8733 Update dependency qreator to v9.7.0 2024-10-26 18:58:20 -04:00
renovate[bot]
c4b42d32b9 Update dependency markdown-it-ruby to v1.1.0 2024-10-26 14:32:49 -04:00
renovate[bot]
9fd5bd5319 Update dependency markdown-it-ruby to v1 2024-10-26 11:58:49 -04:00
renovate[bot]
2c5b1f2ced Update typescript-eslint monorepo to v8.11.0 2024-10-26 05:30:34 -04:00
renovate[bot]
3a331f65e7 Update dependency qreator to v9.6.0 2024-10-26 05:29:30 -04:00
renovate[bot]
6bbae080d7 Update dependency eslint-plugin-jsx-a11y to v6.10.2 2024-10-26 02:10:41 -04:00
renovate[bot]
bfa6354a11 Update dependency @types/node to v22.8.1 2024-10-26 02:10:18 -04:00
renovate[bot]
cb5f1b36f5 Update dependency sass to v1.80.4 2024-10-26 00:28:17 -04:00
renovate[bot]
1125d2a615 Update babel monorepo 2024-10-26 00:27:46 -04:00
renovate[bot]
61ebe0a32b Update dependency i18next to v23.16.4 2024-10-25 22:01:15 -04:00
renovate[bot]
83a1e924ce Update dependency eslint-plugin-jsx-a11y to v6.10.1 2024-10-25 22:00:46 -04:00
renovate[bot]
e560fe6b7f Update dependency i18next to v23.16.2 2024-10-20 09:23:24 -04:00
renovate[bot]
727a1eced5 Update dependency i18next to v23.16.1 2024-10-19 15:24:43 -04:00
renovate[bot]
492b235cd5 Update typescript-eslint monorepo to v8.10.0 2024-10-19 06:09:38 -04:00
renovate[bot]
c8e717acb5 Update eslint monorepo to v9.13.0 2024-10-19 02:38:56 -04:00
renovate[bot]
863b3e2546 Update dependency terser to v5.36.0 2024-10-19 02:37:49 -04:00
renovate[bot]
68e88ba9ec Update dependency sass to v1.80.3 2024-10-19 01:08:27 -04:00
renovate[bot]
1650d1ec70 Update dependency @types/node to v22.7.7 2024-10-19 01:07:24 -04:00
renovate[bot]
9beffed143 Update pnpm to v9.12.2 2024-10-18 20:19:10 -04:00
renovate[bot]
7f9281e62a Update dependency @types/node to v22.7.6 2024-10-18 20:17:49 -04:00
Dessalines
5659cb05a1
Adding nynorsk language. (#2757) 2024-10-16 08:30:43 -04:00
407d1dd6b4 Updating translations. 2024-10-15 17:20:26 -04:00
SleeplessOne1917
0b3720f981
SSO Support - alternate UI (#2731)
* OIDC SSO Support

Co-authored-by: Anthony Lawn <thepaperpilot@gmail.com>

* improvements based on review feedback

* Get started with modal

* Make list for providers

* Plug in providers list

* Create or edit provider modal tweaks

* Provider delete modal

* Fix delete modal

* Add add oauth provider buttons

* Hookup some functions

* Add edit oauth

* Some bug fixes

* Delete old OAuth form

* Tweak login page oauth UI

* pnpm version bump

* Use approach other than modal for login screen SSO providers

* Accomodate translation with key different than error type

* Incorporate translations

* Update src/shared/components/home/oauth/oauth-provider-list-item.tsx

Co-authored-by: privacyguard <92675882+privacyguard@users.noreply.github.com>

---------

Co-authored-by: privacyguard <privacyguard@users.noreply.github.com>
Co-authored-by: Anthony Lawn <thepaperpilot@gmail.com>
Co-authored-by: privacyguard <92675882+privacyguard@users.noreply.github.com>
2024-10-15 16:30:14 -04:00
b05363beb5
Ignore updates for lemmy-js-client (#2753)
* Ignore updates for lemmy-js-client

* Update renovate.json
2024-10-15 14:26:34 -04:00
renovate[bot]
f97df0198b Update dependency i18next to v23.16.0 2024-10-13 12:15:50 -04:00
renovate[bot]
4c6f05a7fa Update dependency cookie to v1 2024-10-12 05:15:45 -04:00
renovate[bot]
6552a69aab Update pnpm to v9.12.1 2024-10-12 02:40:51 -04:00
renovate[bot]
e401c274eb Update dependency cookie-parser to v1.4.7 2024-10-12 00:10:25 -04:00
renovate[bot]
a3754d8cf2 Update dependency cookie to v0.7.2 2024-10-12 00:09:12 -04:00
renovate[bot]
567935c684 Update dependency @types/node to v22.7.5 2024-10-11 22:06:12 -04:00
renovate[bot]
e3266c6535 Update babel monorepo to v7.25.8 2024-10-11 22:05:00 -04:00
renovate[bot]
f2b2a20637 Update typescript-eslint monorepo to v8.8.0 2024-10-05 09:20:01 -04:00
renovate[bot]
e91d9fa659 Update pnpm to v9.12.0 2024-10-05 09:45:24 +00:00
renovate[bot]
22ca14c13b Update dependency i18next to v23.15.2 2024-10-05 09:45:00 +00:00
renovate[bot]
7fa8bda2dc Update eslint monorepo to v9.12.0 2024-10-05 02:26:06 -04:00
renovate[bot]
611e7f2cdf Update dependency qreator to v9.5.1 2024-10-05 02:25:32 -04:00
renovate[bot]
277bc6f77c Update dependency globals to v15.10.0 2024-10-05 00:10:22 -04:00
renovate[bot]
eee2358348 Update dependency cookie to ^0.7.0 2024-10-05 00:08:29 -04:00
renovate[bot]
0757c05308 Update dependency sanitize-html to v2.13.1 2024-10-05 00:06:36 -04:00
renovate[bot]
f00014da97 Update babel monorepo to v7.25.7 2024-10-05 00:45:09 +00:00
8b7a4e72af
Post scheduling (#2693)
* Post scheduling

Adds simple input for scheduling a post x minutes in the future.
Do you want to pull in a date picker library for this?

See https://github.com/LemmyNet/lemmy/pull/5025

* lint

* add date picker

* Adding i18n, and fixing some fields (#2694)

* fix edits

* cleanup

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-10-02 08:37:41 -04:00
Dessalines
6b5da8cfb1
Fixing titleOnly, PostSort, and CommentSort. (#2715)
* Fixing titleOnly, PostSort, and CommentSort.

* SortType, Tagline, Emojis (#2718)

* PostSortType

* Hide post sort types in comment view

* Tagline

* CustomEmoji

* Update lemmy-js-client to 0.20.0-alpha.17

* Prompt before leaving unsaved forms

* Add cancel buttons, only create taglines when saving

* Cleanup SortSelect

* Use markdown url for custom emojis

This prevent SSR and CSR from rendering different images after changing
the image of an emoji, already posted emojis will keep showing the old
image. This will also display the same image on different instances that
have overlapping custom emojis.

* Cleanup EmojisForm sorting

* Use existing CommentSortSelect

* Simpler sort type conversion

---------

Co-authored-by: matc-pub <161147791+matc-pub@users.noreply.github.com>
2024-10-01 14:40:02 -04:00
Pastel de N4ta
6057c96f0c
UX - Swap "Select Language" and "Cancel/Preview/Reply" button locations around in commentsReverse order of buttons in Reply TextArea (#2730)
* Reverse order of buttons in Reply TextArea

Fixes #1924

* Update markdown-textarea.tsx

Oopsie

* I'm so confused

Reverse last commit

* Update markdown-textarea.tsx

??

* Update markdown-textarea.tsx

* Lint markdown-textarea.tsx
2024-10-01 12:08:36 -04:00
SleeplessOne1917
8a500f3ddf
Fix jump to content (#2729) 2024-09-30 23:00:54 -04:00
Dessalines
7be8d1d8eb
Running renovate every weekend. (#2728) 2024-09-30 22:12:40 -04:00
renovate[bot]
3823e38081 Update typescript-eslint monorepo to v8.7.0 2024-09-30 08:43:18 -04:00
renovate[bot]
17ac216e4b Update dependency webpack to v5.95.0 2024-09-30 08:41:51 -04:00
renovate[bot]
43e03219fa Update dependency terser to v5.34.1 2024-09-30 07:27:21 -04:00
renovate[bot]
c804e107e9 Update dependency @types/node to v22.7.4 2024-09-30 07:25:20 -04:00
renovate[bot]
131907b8b5 Update eslint monorepo to v9.11.1 2024-09-30 07:23:17 -04:00
renovate[bot]
1789fb371f Update dependency sass to v1.79.4 2024-09-30 01:32:23 +00:00
Dessalines
017df0f2d0
Updating git cliff. (#2719) 2024-09-27 20:23:55 +00:00
matc-pub
626b18d478
fix date-fns dynamic import (#2716) 2024-09-25 14:07:37 +00:00
renovate[bot]
9ff0b93560 Update eslint monorepo to v9.11.0 2024-09-23 11:46:17 -04:00
renovate[bot]
aee7145bc2 Update typescript-eslint monorepo to v8.6.0 2024-09-23 10:23:13 -04:00
renovate[bot]
b1b82c3d6b Update dependency date-fns to v4 2024-09-23 07:47:25 -04:00
renovate[bot]
bc7f83faf0 Update dependency markdown-it-bidi to ^0.2.0 2024-09-23 07:47:01 -04:00
renovate[bot]
f5c48bb6bf Update pnpm to v9.11.0 2024-09-23 07:45:52 -04:00
renovate[bot]
ee35fae3fa Update dependency webpack-dev-server to v5.1.0 2024-09-23 07:45:10 -04:00
renovate[bot]
c8b617b403 Update dependency typescript to v5.6.2 2024-09-23 07:44:24 -04:00
renovate[bot]
5e719c8cec Update dependency terser to v5.33.0 2024-09-23 07:43:40 -04:00
renovate[bot]
f2c1c20efa Update dependency qreator to v9.4.0 2024-09-23 07:43:18 -04:00
renovate[bot]
f9183bf0dc Update dependency sass to v1.79.3 2024-09-23 07:42:48 -04:00
renovate[bot]
223d29e419 Update dependency prettier-plugin-organize-imports to v4.1.0 2024-09-23 07:35:30 -04:00
renovate[bot]
6176d718dc Update dependency markdown-it-highlightjs to v4.2.0 2024-09-23 07:34:04 -04:00
renovate[bot]
eb6b407060 Update dependency sass-loader to v16.0.2 2024-09-23 07:13:00 -04:00
renovate[bot]
3d45dab69f Update dependency express to ~4.21.0 2024-09-23 07:12:50 -04:00
renovate[bot]
c187cd34c3 Update dependency i18next to v23.15.1 2024-09-23 07:12:41 -04:00
renovate[bot]
f7bf658a60 Update dependency babel-loader to v9.2.1 2024-09-23 03:07:22 +00:00
Dessalines
8a44c4d38f
Adding a post title only filter to the search page. (#2695)
* Adding a post title only filter to the search page.

- Also fixing restore data on unban.
- Accounting for newly removed block views.

* Only show post title button for All and Post searches.

* Addressing PR comments.
2024-09-20 13:20:39 +00:00
Dessalines
5f535e7dad
Fixing woodpecker warnings. (#2696) 2024-09-18 23:30:48 -04:00
renovate[bot]
10f22f07c7 Update dependency eslint-plugin-jsx-a11y to v6.10.0 2024-09-16 11:08:44 +00:00
renovate[bot]
6340419c47 Update dependency lint-staged to v15.2.10 2024-09-16 06:06:49 +00:00
renovate[bot]
ca16a3121e Update dependency @types/node to v22.5.5 2024-09-15 21:50:26 -04:00
renovate[bot]
267126bd35 Update dependency husky to v9.1.6 2024-09-15 21:50:14 -04:00
renovate[bot]
827deb4e01 Update dependency babel-plugin-inferno to v6.7.2 2024-09-09 06:20:20 +00:00
renovate[bot]
550cdbfaf1 Update dependency @types/node to v22.5.4 2024-09-09 04:47:16 +00:00
renovate[bot]
e263f7d716 Update dependency @babel/runtime to v7.25.6 2024-09-01 21:32:12 -04:00
renovate[bot]
43aea06bec Update dependency @types/node to v22.5.2 2024-09-01 21:32:00 -04:00
Dessalines
66c1376ac0
Separating login and register links for must login alert. (#2679)
- Fixes #2624
2024-08-28 16:40:03 -04:00
Dessalines
22eda995d3
Dont simplify cross-posts for a community feed. (#2678)
- Fixes #2104
2024-08-28 16:33:47 -04:00
631c2cda4a 0.19.6-beta.7 2024-08-27 16:31:49 -04:00
6580e1aaed Updating translations. 2024-08-27 16:31:36 -04:00
Dessalines
2fdd42087a
Fixing peertube and ordinary video embeds. (#2676)
- Fixes #709
2024-08-27 15:35:12 -04:00
Dessalines
8c68ee450e
Changing sameSite cookie from Strict to Lax. (#2677)
- Fixes #2666
- Fixes #2601
2024-08-27 15:31:31 -04:00
Dessalines
d4b6f29729
Remove show new post notifs setting. (#2675)
- Fixes #2667
2024-08-27 15:00:24 -04:00
Anon
d69dd1531b
Fix memory leak around emojis on server render (#2674) 2024-08-27 13:10:19 -04:00
SleeplessOne1917
f456610930
Enable spellcheck for markdown text area (#2669)
* Enable spellcheck for markdown text area

* Another dep bump
2024-08-27 13:08:37 -04:00
renovate[bot]
76219b9067 Update eslint monorepo to v9.9.1 2024-08-26 08:42:21 -04:00
renovate[bot]
c7f10f1efb Update dependency prettier-plugin-packagejson to v2.5.2 2024-08-26 09:44:58 +00:00
renovate[bot]
8c0878e8c3 Update dependency husky to v9.1.5 2024-08-26 08:35:33 +00:00
renovate[bot]
0d795581b5 Update dependency @types/path-browserify to v1.0.3 2024-08-26 03:45:26 +00:00
252695ea6a 0.19.6-beta.6 2024-08-19 19:42:49 -04:00
SleeplessOne1917
42637b96d3
Pre release dep bump (#2661)
* Pre release dep bump

* Upping again.

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-08-19 19:41:52 -04:00
fbcb6b55c5 Updating translations. 2024-08-19 19:40:41 -04:00
renovate[bot]
5a1878c482 Update dependency @types/node to v22.4.1 2024-08-19 13:38:59 +00:00
renovate[bot]
a3ea569b33 Update dependency terser to v5.31.6 2024-08-19 11:52:11 +00:00
renovate[bot]
a141871f4a Update dependency sharp to v0.33.5 2024-08-19 08:34:04 +00:00
renovate[bot]
202dc7c6df Update dependency lint-staged to v15.2.9 2024-08-19 03:12:43 +00:00
b5c6caef47 Updating translations. 2024-08-15 08:53:22 -04:00
ba959c5cae 0.19.6-beta.5 2024-08-15 08:49:29 -04:00
renovate[bot]
2b726b51b5 Update dependency terser to v5.31.5 2024-08-11 22:40:02 -04:00
renovate[bot]
19177bdacf Update typescript-eslint monorepo to v8.0.1 2024-08-11 22:39:45 -04:00
1a084a964d 0.19.6-beta.4 2024-08-08 20:02:00 -04:00
Dessalines
9c4424f792
Add ability to fill magnet link title on post creation. (#2654) 2024-08-08 20:01:48 -04:00
62c62bf038 0.19.6-beta.3 2024-08-06 10:31:07 -04:00
7fc6216a2a Updating translations. 2024-08-06 10:30:58 -04:00
SleeplessOne1917
0651b216d2
Registration application view (#2651)
* Fix tribute

* Group modals into common directory

* Make display modal

* Make registration applications viewable

* Remove potential crash

* Add translations
2024-08-04 11:33:23 -04:00
Dessalines
8d5e7de18e
Add torrent help (#2650)
* Updating translations.

* Adding a link to view all a user's moderation history from an item.

- Also making moderation history strings more detailed.

* Fix.

* Adding admin view moderation history to profile page.

* Adding a torrent help message.

* Updating translations.

* Updating translations.
2024-08-03 16:11:31 -04:00
Dessalines
999b083545
More moderation history (#2649)
* Updating translations.

* Adding a link to view all a user's moderation history from an item.

- Also making moderation history strings more detailed.

* Fix.

* Adding admin view moderation history to profile page.

* Adding admin view moderation history to profile page.
2024-08-03 14:56:19 -04:00
SleeplessOne1917
bee4395706
Fix tribute related bug (#2647)
* Fix tribute related bug

* fix api client error
2024-08-01 15:03:16 -04:00
6754c08902 Fixing 2024-08-01 10:45:58 -04:00
renovate[bot]
fa3b2488fd Update typescript-eslint monorepo to v8 2024-08-01 10:45:58 -04:00
24ddec4ee0 Updating translations. 2024-08-01 08:28:09 -04:00
9db79c49d6 0.19.6-beta.2 2024-08-01 08:28:03 -04:00
renovate[bot]
0763fb688a Update dependency @babel/preset-env to v7.25.3 2024-08-01 08:21:16 -04:00
Dessalines
7718ee7a80
Adding renovate automerge. (#2644) 2024-07-30 09:49:04 -04:00
SleeplessOne1917
66277e6ece
Remove min and max length from password input when using login form (#2643) 2024-07-30 09:48:49 -04:00
renovate[bot]
e09127467c Update babel monorepo to v7.25.2 2024-07-30 00:29:15 -04:00
renovate[bot]
876d892f8c Update typescript-eslint monorepo to v7.18.0 2024-07-29 14:53:32 -04:00
renovate[bot]
f4accddbda Update dependency husky to v9.1.4 2024-07-29 11:13:10 -04:00
Dessalines
72a3a3dbb9
Remove trending communities card from home. (#2639)
* Remove trending communities card from home.

- This proved after time to be pointless. I thought about keeping the
  create and explore communities buttons, but these are already in the
  navbar, and so just take up more pointless space.
- Fixes #2027
- Fixes #2007

* Forgot to remove trending loading skeleton.
2024-07-29 15:10:21 +00:00
renovate[bot]
a5a696e54f Update babel monorepo to v7.25.0 2024-07-29 09:13:48 -04:00
SleeplessOne1917
e9c6e99165
Set data-bs-theme based on the presence of "dark" in theme name (#2638) 2024-07-29 09:11:05 -04:00
renovate[bot]
e9ba38dedf Update dependency @types/node to v20.14.13 2024-07-28 09:59:11 -04:00
renovate[bot]
9f54a25e26 Update dependency sass-loader to v16 2024-07-26 22:00:33 -04:00
renovate[bot]
0ab51cf52b Update eslint monorepo to v9.8.0 2024-07-26 18:03:28 -04:00
renovate[bot]
ef3b35393f Update dependency husky to v9.1.3 2024-07-26 14:22:11 -04:00
renovate[bot]
8abae1973b Update dependency husky to v9.1.2 2024-07-25 12:09:15 -04:00
renovate[bot]
036e925593 Update dependency @types/markdown-it to v14.1.2 2024-07-25 01:21:51 -04:00
Dessalines
81dc9a2511
Fixing modlog filtering to allow admins and mods to filter by mod. (#2629)
- Fixes #2590

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-07-23 16:24:19 -04:00
renovate[bot]
37f4944dc2 Update dependency @types/node to v20.14.12 2024-07-23 15:17:00 -04:00
renovate[bot]
4a65fd888d Update dependency sass-loader to v15 2024-07-23 13:24:14 -04:00
renovate[bot]
c73345f07e Update dependency typescript to v5.5.4 2024-07-22 22:34:21 -04:00
renovate[bot]
966faede3b Update dependency typescript-eslint to v7.17.0 2024-07-22 17:04:10 -04:00
renovate[bot]
a024e3301a Update typescript-eslint monorepo to v7.17.0 2024-07-22 13:26:54 -04:00
SleeplessOne1917
5c3da58366
Fix issue from logo bugfix (#2620)
* Fix issue from previous bugfix

* Fix typo
2024-07-22 10:19:11 -04:00
SleeplessOne1917
5d124a3e14
Make more post params cross-postable (#2621) 2024-07-22 09:29:59 -04:00
SleeplessOne1917
cd15d3c132
Fix wonky comment action icon button alignment (#2622) 2024-07-22 09:29:26 -04:00
renovate[bot]
c4d0a06f9e Update pnpm to v9.6.0 2024-07-21 22:45:24 -04:00
SleeplessOne1917
01c095d269
Prevent broken logo from crashing site (#2619)
* Prevent broken logo from crashing site

* Cache icons in memory to make site less vulnerable to DOS attacks

* Swap try and if blocks for apple touch icon
2024-07-21 14:56:57 -04:00
Dessalines
ab51245ca5
Add rate limit info message. (#2563)
* Updating translations.

* Adding a rate limit info message.

- Fixes #1920

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-07-20 21:20:12 -04:00
renovate[bot]
d67b43a043 Update dependency i18next to v23.12.2 2024-07-19 07:57:59 -04:00
Dessalines
5cd66bb037
Removing renovate from git cliff (#2564)
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-07-18 12:17:09 -04:00
renovate[bot]
c1e22bd7d5 Update dependency husky to v9.1.1 2024-07-18 11:17:14 -04:00
renovate[bot]
67a591a08c Update dependency husky to v9.1.0 2024-07-17 13:04:55 -04:00
renovate[bot]
d517abd597 Update dependency eslint-plugin-prettier to v5.2.1 2024-07-17 10:56:26 -04:00
renovate[bot]
71f6856329 Update dependency @types/node to v20.14.11 2024-07-16 20:31:09 -04:00
renovate[bot]
40c4269703 Update dependency terser to v5.31.3 2024-07-16 13:50:38 -04:00
renovate[bot]
7329db55f3 Update dependency prettier-plugin-packagejson to v2.5.1 2024-07-16 04:59:52 -04:00
renovate[bot]
0f1e1a3dd6 Update typescript-eslint monorepo to v7.16.1 2024-07-15 14:20:28 -04:00
d6a31ec46f Fixing lint. 2024-07-15 10:02:55 -04:00
renovate[bot]
492d86457c Update dependency prettier to v3.3.3 2024-07-15 10:02:55 -04:00
renovate[bot]
a53aba545b Update dependency @babel/core to v7.24.9 2024-07-15 08:36:37 -04:00
renovate[bot]
df58dcc6ad Update dependency i18next to v23.12.1 2024-07-14 11:40:19 -04:00
renovate[bot]
6790fbac32 Update dependency i18next to v23.12.0 2024-07-14 08:15:48 -04:00
renovate[bot]
7a30476d89 Update eslint monorepo to v9.7.0 2024-07-12 17:45:49 -04:00
renovate[bot]
02a70beeef Update dependency sass to v1.77.8 2024-07-11 18:31:07 -04:00
renovate[bot]
7b97cf9177 Update dependency webpack to v5.93.0 2024-07-11 16:20:12 -04:00
renovate[bot]
f51b03ed79 Update babel monorepo to v7.24.8 2024-07-11 11:45:10 -04:00
renovate[bot]
c1b94f5c21 Update dependency rimraf to v6.0.1 2024-07-10 14:12:36 -04:00
renovate[bot]
85cbc5bc71 Update dependency terser to v5.31.2 2024-07-10 07:35:43 -04:00
renovate[bot]
cf3e506a8c Update dependency sass to v1.77.7 2024-07-09 20:08:14 -04:00
renovate[bot]
7746ee3c2e Update dependency rimraf to v6 2024-07-09 01:08:22 -04:00
renovate[bot]
d02962b17a Update dependency rimraf to v5.0.9 2024-07-09 00:00:36 -04:00
renovate[bot]
be3f631edc Update dependency qs to v6.12.3 2024-07-08 20:11:12 -04:00
renovate[bot]
5cd399a4b1 Update typescript-eslint monorepo to v7.16.0 2024-07-08 15:13:51 -04:00
renovate[bot]
e324252963 Update pnpm to v9.5.0 2024-07-07 16:18:56 -04:00
renovate[bot]
834b7e4a69 Update dependency highlight.js to v11.10.0 2024-07-06 19:11:26 -04:00
renovate[bot]
318d61e7cf Update dependency rimraf to v5.0.8 2024-07-06 01:37:57 -04:00
renovate[bot]
ecb2637bdc Update dependency @types/node to v20.14.10 2024-07-05 16:00:20 -04:00
renovate[bot]
eaa337eef3 Update dependency globals to v15.8.0 2024-07-02 08:54:40 -04:00
renovate[bot]
f4aa9b22a1 Update dependency prettier-plugin-organize-imports to v4 2024-07-01 23:25:39 -04:00
renovate[bot]
4d4b18d9c5 Update dependency typescript to v5.5.3 2024-07-01 20:08:29 -04:00
renovate[bot]
2d3d434873 Update dependency qs to v6.12.2 2024-07-01 17:13:22 -04:00
renovate[bot]
acd4e5a7f4 Update typescript-eslint monorepo to v7.15.0 2024-07-01 14:21:02 -04:00
renovate[bot]
75b5852aa6 Update dependency globals to v15.7.0 2024-06-30 08:44:30 -04:00
renovate[bot]
790fef60cc Update eslint monorepo to v9.6.0 2024-06-28 15:53:47 -04:00
renovate[bot]
1016d379be Update dependency @types/node to v20.14.9 2024-06-25 18:46:22 -04:00
renovate[bot]
98fc9e2c66 Update typescript-eslint monorepo to v7.14.1 2024-06-24 14:46:38 -04:00
renovate[bot]
0ede7b3d13 Update dependency @types/node to v20.14.8 2024-06-22 05:51:21 -04:00
renovate[bot]
c2356cda3a Update dependency @types/node to v20.14.7 2024-06-20 18:43:24 -04:00
renovate[bot]
b677179317 Update dependency typescript to v5.5.2 2024-06-20 16:11:34 -04:00
renovate[bot]
680488b51f Update dependency eslint-plugin-jsx-a11y to v6.9.0 2024-06-20 03:59:24 -04:00
matc-pub
b1604e9c9f
Fix getQueryString (#2558)
Browsers without size property for URLSearchParams always returned an
empty string.

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-06-19 15:37:04 -04:00
renovate[bot]
3bcb12eff9 Update dependency webpack to v5.92.1 2024-06-19 14:23:21 -04:00
renovate[bot]
1299f7ce07 Update dependency @types/node to v20.14.6 2024-06-19 12:20:36 -04:00
253f0d9b18 0.19.5 2024-06-19 08:16:19 -04:00
SleeplessOne1917
ede761f6c8
Leave admin confirmation (#2556)
* Add confirmmation modal for leaving admin team

* Add translations
2024-06-18 13:05:07 -04:00
4f37a3bee3 0.19.5-alpha.3 2024-06-18 11:48:31 -04:00
SleeplessOne1917
24adbf71bb
Regenerate themes (#2554) 2024-06-18 08:27:22 -04:00
renovate[bot]
ab5d45199b Update dependency @types/node to v20.14.5 2024-06-18 05:13:24 -04:00
a4f1777014 0.19.5-alpha.2 2024-06-17 20:25:27 -04:00
renovate[bot]
fbbe66a2af Update dependency @types/node to v20.14.4 2024-06-17 19:52:22 -04:00
SleeplessOne1917
4293c5a6c3
Fix auto download bug (#2552) 2024-06-17 19:50:17 -04:00
renovate[bot]
1ee1bdeaa9 Update dependency sass to v1.77.6 2024-06-17 18:32:36 -04:00
renovate[bot]
415d303b93 Update dependency @types/node to v20.14.3 2024-06-17 18:11:12 -04:00
renovate[bot]
bcce1ecda9 Update typescript-eslint monorepo to v7.13.1 2024-06-17 14:48:59 -04:00
renovate[bot]
1a9e317412 Update pnpm to v9.4.0 2024-06-17 14:30:04 -04:00
SleeplessOne1917
1109e23dd5
Remove ignore scripts (#2545) 2024-06-17 14:27:28 -04:00
renovate[bot]
0bd5939a7e Update dependency globals to v15.6.0 2024-06-17 08:31:29 -04:00
renovate[bot]
06aae28094 Update dependency globals to v15.5.0 2024-06-15 23:29:40 -04:00
a09e1e1d8e 0.19.5-alpha.1 2024-06-15 15:11:46 -04:00
SleeplessOne1917
2f91700074
Make uploads only viewable to oneself (#2540) 2024-06-14 21:16:17 -04:00
renovate[bot]
35b3cec2d4 Update eslint monorepo to v9.5.0 2024-06-14 18:48:14 -04:00
renovate[bot]
f984acaa5c Update dependency lint-staged to v15.2.7 2024-06-12 16:37:38 -04:00
renovate[bot]
30b228ef0f Update dependency sass to v1.77.5 2024-06-11 20:26:24 -04:00
renovate[bot]
d44e257163 Update dependency lint-staged to v15.2.6 2024-06-11 15:56:02 -04:00
renovate[bot]
7c789e9213 Update dependency webpack to v5.92.0 2024-06-11 11:43:41 -04:00
renovate[bot]
c5cf6d1c7b Update dependency prettier to v3.3.2 2024-06-11 05:46:47 -04:00
renovate[bot]
7e215cb2b5 Update typescript-eslint monorepo to v7.13.0 2024-06-10 15:43:47 -04:00
renovate[bot]
dd3e286ef8 Update pnpm to v9.3.0 2024-06-10 09:24:07 -04:00
matc-pub
661a8000cd
Handle CrossPostParams when mounting CreatePost (#2521) 2024-06-08 11:45:56 -04:00
matc-pub
761cd7aa4e
Keep CreatePost mounted when updating URL, fixes focus resets. (#2520) 2024-06-07 15:51:35 -04:00
matc-pub
300ed5fe03
Move fetchPageTitle into componentWillMount and only call client side. (#2519) 2024-06-07 14:54:53 -04:00
66c60d9f14 0.19.4 2024-06-07 13:24:30 -04:00
99df628f55 Revert "0.19.4"
This reverts commit 205adad8ba.
2024-06-07 13:24:01 -04:00
205adad8ba 0.19.4 2024-06-07 13:23:25 -04:00
425a6fc36c Revert "0.19.4"
This reverts commit 77aa0c0753.
2024-06-07 13:22:29 -04:00
77aa0c0753 0.19.4 2024-06-07 13:22:02 -04:00
11f9a35abf Revert "0.19.4"
This reverts commit 46fd196019.
2024-06-07 13:21:38 -04:00
SleeplessOne1917
c9b7f66925
Fix copy title from metadata (#2518)
* Fix copy title bug

* Refactor

* Update translations

* Fix it for real this time

* Remove query params when field is empty
2024-06-07 13:21:04 -04:00
46fd196019 0.19.4 2024-06-07 07:50:57 -04:00
f79995a80d 0.19.4-rc.11 2024-06-06 23:01:01 -04:00
SleeplessOne1917
7003b564a3
Create post query params (#2515)
* Allow create post page form inputs to be populated from query params

* Fix issue with cross post params.

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-06-06 22:56:17 -04:00
06b5925e85 0.19.4-rc.10 2024-06-06 20:45:03 -04:00
Dessalines
3075185a7a
Upping lemmy-js-client_version. (#2513) 2024-06-06 19:12:04 -04:00
renovate[bot]
9a72a48c6b Update dependency lemmy-js-client to v0.19.4 2024-06-06 18:53:16 -04:00
renovate[bot]
3bc4302ef0 Update dependency globals to v15.4.0 2024-06-06 12:27:25 -04:00
SleeplessOne1917
ed12fb5fe0
Bump translation submodule (#2511) 2024-06-06 12:25:17 -04:00
6dd3f707c3 0.19.4-rc.9 2024-06-06 10:42:48 -04:00
renovate[bot]
e9850bb22e Update dependency terser to v5.31.1 2024-06-06 09:12:43 -04:00
c567977205 0.19.4-rc.8 2024-06-05 19:00:17 -04:00
renovate[bot]
8a6b074509 Update dependency qreator to v9.3.0 2024-06-05 17:52:59 -04:00
8354132677 0.19.4-rc.7 2024-06-05 17:30:00 -04:00
9335ade6c4 Updating translations. 2024-06-05 17:29:39 -04:00
renovate[bot]
51cbd2e8bb Update dependency qreator to v9.2.1 2024-06-05 14:26:32 -04:00
SleeplessOne1917
4d07ca7662
Update deps and package manager (#2505) 2024-06-05 11:24:28 -04:00
renovate[bot]
68ce9b17fe Update dependency @types/node to v20.14.2 2024-06-05 10:40:23 -04:00
renovate[bot]
d1540db533 Update dependency prettier to v3.3.1 2024-06-05 07:22:02 -04:00
Dessalines
66121ec7a9
Adding @matc-pub to codeowners. (#2500) 2024-06-04 15:24:17 +00:00
d1053e4b8a 0.19.4-rc.6 2024-06-04 08:31:18 -04:00
renovate[bot]
f33711acb0 Update dependency @types/node to v20.14.1 2024-06-03 21:16:44 -04:00
Dessalines
18c3f3975f
Fix 502 error on initial settings page load. (#2498)
- Needed to wrap the fetchThemeList in an isBrowser check.
- Fixes #2497
2024-06-03 22:31:54 +00:00
renovate[bot]
e3a11648c9 Update typescript-eslint monorepo to v7.12.0 2024-06-03 16:14:49 -04:00
renovate[bot]
a02a2341d1 Update dependency @types/node to v20.14.0 2024-06-02 19:55:01 -04:00
matc-pub
02fcfa26ee
Fix some submit button issues (#2487)
* Prevent PostForm submit button spam

* Keep CreatePost PostForm visible during submission

* Keep PostListing PostForm visible during submission

* Keep PostForm navigation warning enabled during submission

* Remove `finished` from MarkdownTextAreaProps

* Handle CommentForm submission failures

* Keep CommentForm navigation warning enabled during submission

* Handle PrivateMessageForm submission failures

* Bypass navigation warning for successful CreatePrivateMessage

* Fix absolute import, add eslint rule

* Cleaner handleCommentSubmit

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-06-02 11:46:32 -04:00
renovate[bot]
14ae45fe95 Update typescript-eslint monorepo to v7.11.0 2024-06-01 15:59:05 -04:00
renovate[bot]
cdd902abde Update dependency prettier to v3.3.0 2024-06-01 14:55:46 -04:00
renovate[bot]
8e68524d15 Update eslint monorepo to v9.4.0 2024-06-01 14:47:59 -04:00
renovate[bot]
22397879ce Update dependency qreator to v9.2.0 2024-06-01 14:39:19 -04:00
renovate[bot]
28dfb69c9a Update dependency @types/node to v20.13.0 2024-06-01 14:36:02 -04:00
renovate[bot]
1c246c85e7 Update pnpm to v9.1.4 2024-06-01 14:33:30 -04:00
Dessalines
e3c19c947e
Remove renovate schedule. (#2488)
* Remove renovate schedule.

* Add automerge = true.
2024-06-01 14:29:17 -04:00
50cd984ae4 0.19.4-rc.5 2024-06-01 13:34:52 -04:00
renovate[bot]
1994618a21
Update dependency sass to v1.77.4 (#2486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:30:19 -04:00
renovate[bot]
9fa4d59f10
Update dependency lint-staged to v15.2.5 (#2485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:30:08 -04:00
renovate[bot]
83ea77e0fb
Update dependency css-loader to v7.1.2 (#2484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:15:58 -04:00
renovate[bot]
2668533196
Update babel monorepo to v7.24.6 (#2483)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:15:41 -04:00
16b72778cd 0.19.4-rc.4 2024-05-30 16:08:15 -04:00
f2302145ec 0.19.4-rc.3 2024-05-27 09:37:00 -04:00
e3814064a7 0.19.4-rc.2 2024-05-23 12:08:55 -04:00
7d93959009 Updating translations. 2024-05-23 12:08:44 -04:00
matc-pub
b7fe70d8c1
Render more while reloading only some resources (#2480)
* AdminSettings remove unused currentTab state

* Fix amAdmin check in reports fetchInitialData

* Make CreatePost render earlier

* Include children of auth and anonymous guard in first render.

* Convert DidMount to WillMount where things don't depend on the DOM

`componentDidMount` is called after the first render. A lot of
components used it to call `setState`, which causes a second render.

* Keep route components mounted during same route navigation

Not sure why this wasn't the case without this change. The only
difference here is that the same array is reused in every render.

* Disable mounted same route navigation by default

* Enable mounted same route navigation for some routes

* Render more while loading

* Prettier markup

* Make Post use query params and reload comments independently

* Fix issue with <Prompt /> for forms that remain mounted after "leaving".

* Make Search not rerender the results on every keystroke

* Discard old requests

These used to (mostly) arrive at the old already unmounted components.
Now they would render briefly until the latest response is received.

* Move non breaking space to modlog

* Make show optional for modals
2024-05-22 15:46:13 -04:00
937fd3eb4e 0.19.4-rc.1 2024-05-22 08:57:29 -04:00
31ff759929 0.19.4-beta.8 2024-05-22 10:36:39 +02:00
renovate[bot]
ed3f280c48
Update dependency i18next to v23.11.5 (#2477)
* Update dependency i18next to v23.11.5

* Update dependency i18next to v23.11.5

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-05-21 14:29:37 -04:00
renovate[bot]
57d01928b3
Update typescript-eslint monorepo to v7.10.0 (#2479)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 14:29:00 -04:00
renovate[bot]
2069566ab9
Update pnpm to v9.1.2 (#2478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 14:25:31 -04:00
SleeplessOne1917
1f7c8dd1b0
Fix video thumbnail override issue (#2474)
* Fix video thumbnail override issue

* Cleanup

* Make thumbnails always show up, even on mobile

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-05-21 14:25:21 -04:00
SleeplessOne1917
139514cac8
Use non-deprecated QR library (#2475) 2024-05-21 14:20:32 -04:00
renovate[bot]
7875a793b3
Update dependency @types/markdown-it to v14 (#2470)
* Update dependency @types/markdown-it to v14

* Fixing markdown import.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-05-15 13:46:26 -04:00
c6d1e06fe9 Updating translations. 2024-05-15 13:42:05 -04:00
renovate[bot]
9e94d404f8
Update dependency eslint to v9 (#2472)
* Update dependency eslint to v9

* Fixing eslint.

* Forgot to add file.

* Fixing

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-05-15 13:30:03 -04:00
renovate[bot]
67dd5d164f
Update dependency rimraf to v5.0.7 (#2464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 11:07:18 -04:00
renovate[bot]
d07ed0a90a
Update dependency style-loader to v4 (#2473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 11:07:00 -04:00
renovate[bot]
e7bc05a446
Update dependency css-loader to v7 (#2471)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 11:06:50 -04:00
renovate[bot]
3d65ff6c02
Update typescript-eslint monorepo to v7.9.0 (#2469)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 11:06:41 -04:00
renovate[bot]
52b814945e
Update dependency terser to v5.31.0 (#2468)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:48:42 -04:00
renovate[bot]
44ca1d14b7
Update dependency sass to v1.77.1 (#2467)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:48:29 -04:00
renovate[bot]
007fdb3e85
Update dependency emoji-mart to v5.6.0 (#2466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:48:17 -04:00
renovate[bot]
8599afcd11
Update dependency @emoji-mart/data to v1.2.1 (#2465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:47:56 -04:00
renovate[bot]
51aa169ca3
Update dependency i18next to v23.11.4 (#2463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:47:45 -04:00
renovate[bot]
633a83cc7c
Update dependency @types/node to v20.12.12 (#2462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:47:34 -04:00
renovate[bot]
a6882ce693
Update dependency @types/markdown-it to v13.0.8 (#2461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:47:20 -04:00
renovate[bot]
403b5292e5
Update babel monorepo to v7.24.5 (#2460)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 10:47:10 -04:00
renovate[bot]
2146883168
Configure Renovate (#2458)
* Add renovate.json

* Updating schedule.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-05-15 10:08:38 -04:00
SleeplessOne1917
da5a740fd4
Fix Language not allowed infinite loading on failed post (#2457)
* Fix language not allowed bug

* Add translations
2024-05-14 22:35:54 -04:00
SleeplessOne1917
b793697f68
Refresh admin settings on submit (#2456) 2024-05-14 09:48:59 -04:00
SleeplessOne1917
d705f3685f
Fix leap year issue (#2453)
* Fix leap year issue

* Show same cake day date independent of timezone (#2455)

* Show same cake day date independent of timezone

* Remove commented out assertions

---------

Co-authored-by: matc-pub <161147791+matc-pub@users.noreply.github.com>
2024-05-13 22:45:33 -04:00
129 changed files with 24153 additions and 6619 deletions

View file

@ -1,9 +0,0 @@
generate_translations.js
webpack.config.js
src/shared/build-config.js
src/api_tests
**/*.png
**/*.css
**/*.scss
**/*.svg
src/shared/translations/**

View file

@ -1,55 +0,0 @@
{
"root": true,
"env": {
"browser": true
},
"plugins": ["@typescript-eslint", "jsx-a11y", "prettier"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:inferno/recommended",
"plugin:jsx-a11y/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"warnOnUnsupportedTypeScriptVersion": false
},
"rules": {
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_" }
],
"arrow-body-style": 0,
"curly": 0,
"eol-last": 0,
"eqeqeq": "error",
"func-style": 0,
"import/no-duplicates": 0,
"max-statements": 0,
"max-params": 0,
"new-cap": 0,
"no-console": 0,
"no-duplicate-imports": 0,
"no-extra-parens": 0,
"no-return-assign": 0,
"no-throw-literal": 0,
"no-trailing-spaces": 0,
"no-unused-expressions": 0,
"no-useless-constructor": 0,
"no-useless-escape": 0,
"no-var": 0,
"prefer-const": "error",
"prefer-rest-params": 0,
"prettier/prettier": "error",
"quote-props": 0,
"unicorn/filename-case": 0,
"jsx-a11y/media-has-caption": 0,
"jsx-a11y/label-has-associated-control": 0
}
}

2
.github/CODEOWNERS vendored
View file

@ -1 +1 @@
* @dessalines @SleeplessOne1917
* @dessalines @SleeplessOne1917 @matc-pub

View file

@ -38,10 +38,13 @@ steps:
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy-ui
dockerfile: Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
platforms: linux/amd64, linux/arm64
tag: ${CI_COMMIT_TAG}
when:
@ -49,10 +52,13 @@ steps:
nightly_build:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy-ui
dockerfile: Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
platforms: linux/amd64, linux/arm64
tag: dev
when:

3342
CHANGELOG.md Normal file

File diff suppressed because it is too large Load diff

View file

@ -14,18 +14,19 @@ body = """
{%- if version %} in {{ version }}{%- endif -%}
{% for commit in commits %}
{% if commit.github.pr_title -%}
{%- set commit_message = commit.github.pr_title -%}
{% if commit.remote.pr_title -%}
{%- set commit_message = commit.remote.pr_title -%}
{%- else -%}
{%- set commit_message = commit.message -%}
{%- endif -%}
* {{ commit_message | split(pat="\n") | first | trim }}\
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}
{% if commit.github.pr_number %} in \
[#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}) \
{% if commit.remote.username %} by @{{ commit.remote.username }}{%- endif -%}
{% if commit.remote.pr_number %} in \
[#{{ commit.remote.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.remote.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- if github -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
{% raw %}\n{% endraw -%}
## New Contributors
@ -36,6 +37,7 @@ body = """
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- endif -%}
{% if version %}
{% if previous.version %}
@ -51,7 +53,7 @@ body = """
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
# template for the changelog footer
footer = """
<!-- generated by git-cliff -->
"""
@ -70,16 +72,12 @@ commit_preprocessors = [
# remove issue numbers from commits
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
commit_parsers = [
{ field = "author.name", pattern = "renovate", skip = true },
{ field = "message", pattern = "Upping version", skip = true },
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# regex for matching git tags
tag_pattern = "v[0-9].*"
# regex for skipping tags
skip_tags = "beta|alpha"
# regex for ignoring tags
ignore_tags = "rc"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order

95
eslint.config.mjs Normal file
View file

@ -0,0 +1,95 @@
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import prettier from "eslint-plugin-prettier/recommended";
import jsxa11y from "eslint-plugin-jsx-a11y";
import inferno from "eslint-plugin-inferno";
export default [
pluginJs.configs.recommended,
...tseslint.configs.recommended,
prettier,
{
plugins: {
inferno: inferno,
rules: inferno.configs.recommended,
},
},
{
plugins: {
"jsx-a11y": jsxa11y,
},
rules: jsxa11y.configs.recommended.rules,
},
{
languageOptions: {
parser: tseslint.parser,
},
},
// For some reason this has to be in its own block
{
ignores: [
"generate_translations.js",
"webpack.config.js",
"src/shared/build-config.js",
"src/api_tests",
"**/*.png",
"**/*.css",
"**/*.scss",
"**/*.svg",
"src/shared/translations/**",
"dist/*",
".yalc/*",
],
},
{
files: ["src/**/*.js", "src/**/*.mjs", "src/**/*.ts", "src/**/*.tsx"],
rules: {
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"explicit-module-boundary-types": 0,
"no-empty-function": 0,
"no-non-null-assertion": 0,
"arrow-body-style": 0,
curly: 0,
"eol-last": 0,
eqeqeq: "error",
"func-style": 0,
"import/no-duplicates": 0,
"max-statements": 0,
"max-params": 0,
"new-cap": 0,
"no-console": 0,
"no-duplicate-imports": 0,
"no-extra-parens": 0,
"no-return-assign": 0,
"no-throw-literal": 0,
"no-trailing-spaces": 0,
"no-unused-expressions": 0,
"no-useless-constructor": 0,
"no-useless-escape": 0,
"no-var": 0,
"prefer-const": "error",
"prefer-rest-params": 0,
"prettier/prettier": "error",
"quote-props": 0,
"unicorn/filename-case": 0,
"jsx-a11y/media-has-caption": 0,
"jsx-a11y/label-has-associated-control": 0,
"no-restricted-imports": [
"error",
{
patterns: [
{
group: ["assets/*", "client/*", "server/*", "shared/*"],
message: "Use relative import instead.",
},
],
},
],
},
},
];

6
generate_changelog.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/sh
set -e
# Adding to CHANGELOG.md
git cliff --output CHANGELOG.md
prettier -w CHANGELOG.md

@ -1 +1 @@
Subproject commit f0ab81deea347c433277a90ae752b10f68473719
Subproject commit 2bde7acef9807d28cb13b6584fc63d7b8a457005

View file

@ -1,7 +1,7 @@
{
"name": "lemmy-ui",
"description": "An isomorphic UI for lemmy",
"version": "0.19.4-beta.7",
"version": "0.19.6-beta.7",
"author": "Dessalines <tyhou13@gmx.com>",
"license": "AGPL-3.0",
"scripts": {
@ -10,7 +10,7 @@
"build:prod": "webpack --env COMMIT_HASH=$(git rev-parse --short HEAD) --mode=production",
"clean": "pnpm rimraf dist",
"dev": "node generate_translations.js && pnpm build:dev --watch",
"lint": "pnpm translations:generate && tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx \"src/**\" && prettier --check \"src/**/*.{ts,tsx,js,css,scss}\"",
"lint": "pnpm translations:generate && tsc --noEmit && pnpm eslint --report-unused-disable-directives && pnpm prettier --check \"src/**/*.{ts,tsx,js,mjs,css,scss}\"",
"prebuild:dev": "pnpm clean && node generate_translations.js",
"prebuild:prod": "pnpm clean && node generate_translations.js",
"prepare": "husky",
@ -25,15 +25,14 @@
"node": ">=8.9.0"
},
"dependencies": {
"@babel/plugin-proposal-decorators": "^7.24.1",
"@babel/plugin-transform-class-properties": "^7.24.1",
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/plugin-transform-typescript": "^7.24.1",
"@babel/preset-env": "^7.24.3",
"@babel/preset-typescript": "^7.24.1",
"@babel/runtime": "^7.24.1",
"@emoji-mart/data": "^1.1.2",
"@shortcm/qr-image": "^9.0.4",
"@babel/plugin-proposal-decorators": "^7.24.7",
"@babel/plugin-transform-class-properties": "^7.25.4",
"@babel/plugin-transform-runtime": "^7.25.4",
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/preset-env": "^7.25.4",
"@babel/preset-typescript": "^7.24.7",
"@babel/runtime": "^7.25.4",
"@emoji-mart/data": "^1.2.1",
"autosize": "^6.0.1",
"babel-loader": "^9.1.3",
"babel-plugin-inferno": "^6.7.1",
@ -41,18 +40,18 @@
"check-password-strength": "^2.0.10",
"classnames": "^2.5.1",
"clean-webpack-plugin": "^4.0.0",
"cookie": "^0.6.0",
"cookie": "^1.0.0",
"cookie-parser": "^1.4.6",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0",
"date-fns": "^3.6.0",
"emoji-mart": "^5.5.2",
"css-loader": "^7.1.2",
"date-fns": "^4.0.0",
"emoji-mart": "^5.6.0",
"emoji-short-name": "^2.0.0",
"express": "~4.19.2",
"highlight.js": "^11.9.0",
"express": "~4.21.0",
"highlight.js": "^11.10.0",
"history": "^5.3.0",
"html-to-text": "^9.0.5",
"i18next": "^23.10.1",
"i18next": "^23.14.0",
"inferno": "^8.2.3",
"inferno-create-element": "^8.2.3",
"inferno-helmet": "^5.2.1",
@ -61,37 +60,39 @@
"inferno-router": "^8.2.3",
"inferno-server": "^8.2.3",
"jwt-decode": "^4.0.0",
"lemmy-js-client": "0.19.4-alpha.18",
"lemmy-js-client": "0.20.0-alpha.17",
"lodash.isequal": "^4.5.0",
"markdown-it": "^14.1.0",
"markdown-it-bidi": "^0.1.0",
"markdown-it-bidi": "^0.2.0",
"markdown-it-container": "^4.0.0",
"markdown-it-emoji": "^3.0.0",
"markdown-it-footnote": "^4.0.0",
"markdown-it-highlightjs": "^4.0.1",
"markdown-it-highlightjs": "^4.1.0",
"markdown-it-html5-embed": "^1.0.0",
"markdown-it-ruby": "^0.1.1",
"markdown-it-ruby": "^1.0.0",
"markdown-it-sub": "^2.0.0",
"markdown-it-sup": "^2.0.0",
"mini-css-extract-plugin": "^2.8.1",
"mini-css-extract-plugin": "^2.9.1",
"qreator": "^9.3.0",
"register-service-worker": "^1.7.2",
"run-node-webpack-plugin": "^1.3.0",
"rxjs": "^7.8.1",
"sanitize-html": "^2.13.0",
"sass": "^1.72.0",
"sass-loader": "^14.1.1",
"sass": "^1.77.8",
"sass-loader": "^16.0.1",
"serialize-javascript": "^6.0.2",
"service-worker-webpack": "^1.0.0",
"sharp": "0.33.3",
"sharp": "0.33.5",
"tippy.js": "^6.3.7",
"toastify-js": "^1.12.0",
"tributejs": "^5.1.3",
"webpack": "^5.91.0",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0"
},
"devDependencies": {
"@babel/core": "^7.24.3",
"@babel/core": "^7.25.2",
"@eslint/js": "^9.9.1",
"@types/autosize": "^4.0.3",
"@types/bootstrap": "^5.2.10",
"@types/cookie": "^0.6.0",
@ -99,35 +100,38 @@
"@types/express": "^4.17.21",
"@types/html-to-text": "^9.0.4",
"@types/lodash.isequal": "^4.5.8",
"@types/markdown-it": "^13.0.7",
"@types/markdown-it-container": "^2.0.9",
"@types/node": "^20.11.30",
"@types/path-browserify": "^1.0.2",
"@types/sanitize-html": "^2.11.0",
"@types/markdown-it": "^14.1.2",
"@types/markdown-it-container": "^2.0.10",
"@types/node": "^22.5.0",
"@types/path-browserify": "^1.0.3",
"@types/sanitize-html": "^2.13.0",
"@types/serialize-javascript": "^5.0.4",
"@types/toastify-js": "^1.12.3",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"eslint": "^8.57.0",
"eslint-plugin-inferno": "^7.33.3",
"eslint-plugin-jsx-a11y": "^6.8.0",
"eslint-plugin-prettier": "^5.1.3",
"husky": "^9.0.11",
"@typescript-eslint/eslint-plugin": "^8.3.0",
"@typescript-eslint/parser": "^8.3.0",
"eslint": "^9.9.1",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-inferno": "^7.34.0",
"eslint-plugin-jsx-a11y": "^6.9.0",
"eslint-plugin-prettier": "^5.2.1",
"globals": "^15.9.0",
"husky": "^9.1.5",
"import-sort-style-module": "^6.0.0",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"lint-staged": "^15.2.9",
"prettier": "^3.3.3",
"prettier-plugin-import-sort": "^0.0.7",
"prettier-plugin-organize-imports": "^3.2.4",
"prettier-plugin-packagejson": "^2.4.12",
"qs": "^6.12.0",
"rimraf": "^5.0.5",
"prettier-plugin-organize-imports": "^4.0.0",
"prettier-plugin-packagejson": "^2.5.2",
"qs": "^6.13.0",
"rimraf": "^6.0.1",
"sortpack": "^2.4.0",
"style-loader": "^3.3.4",
"terser": "^5.29.2",
"typescript": "^5.4.3",
"style-loader": "^4.0.0",
"terser": "^5.31.6",
"typescript": "^5.5.4",
"typescript-eslint": "^8.3.0",
"typescript-language-server": "^4.3.3",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-dev-server": "5.0.4"
"webpack-bundle-analyzer": "^4.10.2",
"webpack-dev-server": "5.1.0"
},
"lint-staged": {
"*.{css, scss}": [
@ -141,7 +145,7 @@
"sortpack"
]
},
"packageManager": "pnpm@9.1.0+sha512.67f5879916a9293e5cf059c23853d571beaf4f753c707f40cb22bed5fb1578c6aad3b6c4107ccb3ba0b35be003eb621a16471ac836c87beb53f9d54bb4612724",
"packageManager": "pnpm@9.12.3+sha512.cce0f9de9c5a7c95bef944169cc5dfe8741abfb145078c0d508b868056848a87c81e626246cb60967cbd7fd29a6c062ef73ff840d96b3c86c40ac92cf4a813ee",
"engineStrict": true,
"importSort": {
".js, .jsx, .ts, .tsx": {

File diff suppressed because it is too large Load diff

7
renovate.json Normal file
View file

@ -0,0 +1,7 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"schedule": ["every weekend"],
"automerge": true,
"ignoreDeps": ["lemmy-js-client"]
}

View file

@ -461,3 +461,15 @@ br.big {
.totp-link {
width: fit-content;
}
.oauth-item details[open] .oauth-item-caret {
transform: rotate(90deg);
}
.default-oauth-providers-section > ul {
list-style: none;
}
em-emoji-picker {
width: 100%;
}

View file

@ -40,8 +40,8 @@ hr.my-3 {
}
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -85,28 +85,28 @@ hr.my-3 {
--bs-danger-rgb: 231, 76, 60;
--bs-light-rgb: 48, 48, 48;
--bs-dark-rgb: 222, 226, 230;
--bs-primary-text-emphasis: #004b38;
--bs-secondary-text-emphasis: #45484c;
--bs-success-text-emphasis: #004b38;
--bs-info-text-emphasis: #153d58;
--bs-warning-text-emphasis: #613e07;
--bs-danger-text-emphasis: #5c1e18;
--bs-primary-text-emphasis: rgb(0, 75.2, 56);
--bs-secondary-text-emphasis: rgb(69.2, 72.4, 75.6);
--bs-success-text-emphasis: rgb(0, 75.2, 56);
--bs-info-text-emphasis: rgb(20.8, 60.8, 87.6);
--bs-warning-text-emphasis: rgb(97.2, 62.4, 7.2);
--bs-danger-text-emphasis: rgb(92.4, 30.4, 24);
--bs-light-text-emphasis: #444;
--bs-dark-text-emphasis: #444;
--bs-primary-bg-subtle: #ccf2e8;
--bs-secondary-bg-subtle: #eff0f2;
--bs-success-bg-subtle: #ccf2e8;
--bs-info-bg-subtle: #d6eaf8;
--bs-warning-bg-subtle: #fdebd0;
--bs-danger-bg-subtle: #fadbd8;
--bs-light-bg-subtle: #fcfcfd;
--bs-primary-bg-subtle: rgb(204, 241.6, 232);
--bs-secondary-bg-subtle: rgb(238.6, 240.2, 241.8);
--bs-success-bg-subtle: rgb(204, 241.6, 232);
--bs-info-bg-subtle: rgb(214.4, 234.4, 247.8);
--bs-warning-bg-subtle: rgb(252.6, 235.2, 207.6);
--bs-danger-bg-subtle: rgb(250.2, 219.2, 216);
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #99e4d1;
--bs-secondary-border-subtle: #dee1e5;
--bs-success-border-subtle: #99e4d1;
--bs-info-border-subtle: #aed6f1;
--bs-warning-border-subtle: #fad7a0;
--bs-danger-border-subtle: #f5b7b1;
--bs-primary-border-subtle: rgb(153, 228.2, 209);
--bs-secondary-border-subtle: rgb(222.2, 225.4, 228.6);
--bs-success-border-subtle: rgb(153, 228.2, 209);
--bs-info-border-subtle: rgb(173.8, 213.8, 240.6);
--bs-warning-border-subtle: rgb(250.2, 215.4, 160.2);
--bs-danger-border-subtle: rgb(245.4, 183.4, 177);
--bs-light-border-subtle: #ebebeb;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
@ -137,7 +137,7 @@ hr.my-3 {
--bs-link-color: #00bc8c;
--bs-link-color-rgb: 0, 188, 140;
--bs-link-decoration: none;
--bs-link-hover-color: #009670;
--bs-link-hover-color: rgb(0, 150.4, 112);
--bs-link-hover-color-rgb: 0, 150, 112;
--bs-code-color: #d63384;
--bs-highlight-color: #dee2e6;
@ -182,44 +182,44 @@ hr.my-3 {
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #292929;
--bs-tertiary-bg-rgb: 41, 41, 41;
--bs-primary-text-emphasis: #66d7ba;
--bs-secondary-text-emphasis: #ced3d7;
--bs-success-text-emphasis: #66d7ba;
--bs-info-text-emphasis: #85c1e9;
--bs-warning-text-emphasis: #f8c471;
--bs-danger-text-emphasis: #f1948a;
--bs-primary-text-emphasis: rgb(102, 214.8, 186);
--bs-secondary-text-emphasis: rgb(205.8, 210.6, 215.4);
--bs-success-text-emphasis: rgb(102, 214.8, 186);
--bs-info-text-emphasis: rgb(133.2, 193.2, 233.4);
--bs-warning-text-emphasis: rgb(247.8, 195.6, 112.8);
--bs-danger-text-emphasis: rgb(240.6, 147.6, 138);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #00261c;
--bs-secondary-bg-subtle: #232426;
--bs-success-bg-subtle: #00261c;
--bs-info-bg-subtle: #0a1e2c;
--bs-warning-bg-subtle: #311f04;
--bs-danger-bg-subtle: #2e0f0c;
--bs-primary-bg-subtle: rgb(0, 37.6, 28);
--bs-secondary-bg-subtle: rgb(34.6, 36.2, 37.8);
--bs-success-bg-subtle: rgb(0, 37.6, 28);
--bs-info-bg-subtle: rgb(10.4, 30.4, 43.8);
--bs-warning-bg-subtle: rgb(48.6, 31.2, 3.6);
--bs-danger-bg-subtle: rgb(46.2, 15.2, 12);
--bs-light-bg-subtle: #303030;
--bs-dark-bg-subtle: #181818;
--bs-primary-border-subtle: #007154;
--bs-secondary-border-subtle: #686d71;
--bs-success-border-subtle: #007154;
--bs-info-border-subtle: #1f5b83;
--bs-warning-border-subtle: #925e0b;
--bs-danger-border-subtle: #8b2e24;
--bs-primary-border-subtle: rgb(0, 112.8, 84);
--bs-secondary-border-subtle: rgb(103.8, 108.6, 113.4);
--bs-success-border-subtle: rgb(0, 112.8, 84);
--bs-info-border-subtle: rgb(31.2, 91.2, 131.4);
--bs-warning-border-subtle: rgb(145.8, 93.6, 10.8);
--bs-danger-border-subtle: rgb(138.6, 45.6, 36);
--bs-light-border-subtle: #444;
--bs-dark-border-subtle: #303030;
--bs-heading-color: inherit;
--bs-link-color: #66d7ba;
--bs-link-hover-color: #85dfc8;
--bs-link-color: rgb(102, 214.8, 186);
--bs-link-hover-color: rgb(132.6, 222.84, 199.8);
--bs-link-color-rgb: 102, 215, 186;
--bs-link-hover-color-rgb: 133, 223, 200;
--bs-code-color: #e685b5;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #333;
--bs-border-color: #444;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #66d7ba;
--bs-form-valid-border-color: #66d7ba;
--bs-form-invalid-color: #f1948a;
--bs-form-invalid-border-color: #f1948a;
--bs-form-valid-color: rgb(102, 214.8, 186);
--bs-form-valid-border-color: rgb(102, 214.8, 186);
--bs-form-invalid-color: rgb(240.6, 147.6, 138);
--bs-form-invalid-border-color: rgb(240.6, 147.6, 138);
}
*,
@ -1951,13 +1951,13 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1965,13 +1965,13 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #eff0f2;
--bs-table-border-color: #bfc0c2;
--bs-table-striped-bg: #e3e4e6;
--bs-table-bg: rgb(238.6, 240.2, 241.8);
--bs-table-border-color: rgb(190.88, 192.16, 193.44);
--bs-table-striped-bg: rgb(226.67, 228.19, 229.71);
--bs-table-striped-color: #000;
--bs-table-active-bg: #d7d8da;
--bs-table-active-bg: rgb(214.74, 216.18, 217.62);
--bs-table-active-color: #000;
--bs-table-hover-bg: #dddee0;
--bs-table-hover-bg: rgb(220.705, 222.185, 223.665);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1979,13 +1979,13 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1993,13 +1993,13 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #d6eaf8;
--bs-table-border-color: #abbbc6;
--bs-table-striped-bg: #cbdeec;
--bs-table-bg: rgb(214.4, 234.4, 247.8);
--bs-table-border-color: rgb(171.52, 187.52, 198.24);
--bs-table-striped-bg: rgb(203.68, 222.68, 235.41);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c1d3df;
--bs-table-active-bg: rgb(192.96, 210.96, 223.02);
--bs-table-active-color: #000;
--bs-table-hover-bg: #c6d8e5;
--bs-table-hover-bg: rgb(198.32, 216.82, 229.215);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2007,13 +2007,13 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fdebd0;
--bs-table-border-color: #cabca6;
--bs-table-striped-bg: #f0dfc6;
--bs-table-bg: rgb(252.6, 235.2, 207.6);
--bs-table-border-color: rgb(202.08, 188.16, 166.08);
--bs-table-striped-bg: rgb(239.97, 223.44, 197.22);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e4d4bb;
--bs-table-active-bg: rgb(227.34, 211.68, 186.84);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ead9c0;
--bs-table-hover-bg: rgb(233.655, 217.56, 192.03);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2021,13 +2021,13 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #fadbd8;
--bs-table-border-color: #c8afad;
--bs-table-striped-bg: #eed0cd;
--bs-table-bg: rgb(250.2, 219.2, 216);
--bs-table-border-color: rgb(200.16, 175.36, 172.8);
--bs-table-striped-bg: rgb(237.69, 208.24, 205.2);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e1c5c2;
--bs-table-active-bg: rgb(225.18, 197.28, 194.4);
--bs-table-active-color: #000;
--bs-table-hover-bg: #e7cbc8;
--bs-table-hover-bg: rgb(231.435, 202.76, 199.8);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2036,12 +2036,12 @@ progress {
.table-light {
--bs-table-color: #fff;
--bs-table-bg: #303030;
--bs-table-border-color: #595959;
--bs-table-striped-bg: #3a3a3a;
--bs-table-border-color: rgb(89.4, 89.4, 89.4);
--bs-table-striped-bg: rgb(58.35, 58.35, 58.35);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #454545;
--bs-table-active-bg: rgb(68.7, 68.7, 68.7);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #404040;
--bs-table-hover-bg: rgb(63.525, 63.525, 63.525);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2050,12 +2050,12 @@ progress {
.table-dark {
--bs-table-color: #000;
--bs-table-bg: #dee2e6;
--bs-table-border-color: #b2b5b8;
--bs-table-striped-bg: #d3d7db;
--bs-table-border-color: rgb(177.6, 180.8, 184);
--bs-table-striped-bg: rgb(210.9, 214.7, 218.5);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c8cbcf;
--bs-table-active-bg: rgb(199.8, 203.4, 207);
--bs-table-active-color: #000;
--bs-table-hover-bg: #cdd1d5;
--bs-table-hover-bg: rgb(205.35, 209.05, 212.75);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2155,7 +2155,7 @@ progress {
.form-control:focus {
color: #fff;
background-color: #444;
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2173,7 +2173,7 @@ progress {
opacity: 1;
}
.form-control:disabled {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
opacity: 1;
}
.form-control::file-selector-button {
@ -2300,7 +2300,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2309,7 +2309,7 @@ textarea.form-control-lg {
background-image: none;
}
.form-select:disabled {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
}
.form-select:-moz-focusring {
color: transparent;
@ -2384,7 +2384,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2431,7 +2431,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2380dec6'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28127.5, 221.5, 197.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2501,7 +2501,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #b3ebdd;
background-color: rgb(178.5, 234.9, 220.5);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2527,7 +2527,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #b3ebdd;
background-color: rgb(178.5, 234.9, 220.5);
}
.form-range::-moz-range-track {
width: 100%;
@ -2634,7 +2634,7 @@ textarea.form-control-lg {
}
.form-floating > :disabled ~ label::after,
.form-floating > .form-control:disabled ~ label::after {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
}
.input-group {
@ -2975,6 +2975,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2988,12 +2991,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -3005,12 +3008,12 @@ textarea.form-control-lg {
--bs-btn-bg: #adb5bd;
--bs-btn-border-color: #adb5bd;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #b9c0c7;
--bs-btn-hover-border-color: #b5bcc4;
--bs-btn-hover-bg: rgb(185.3, 192.1, 198.9);
--bs-btn-hover-border-color: rgb(181.2, 188.4, 195.6);
--bs-btn-focus-shadow-rgb: 147, 154, 161;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #bdc4ca;
--bs-btn-active-border-color: #b5bcc4;
--bs-btn-active-bg: rgb(189.4, 195.8, 202.2);
--bs-btn-active-border-color: rgb(181.2, 188.4, 195.6);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #adb5bd;
@ -3022,12 +3025,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -3039,12 +3042,12 @@ textarea.form-control-lg {
--bs-btn-bg: #3498db;
--bs-btn-border-color: #3498db;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #2c81ba;
--bs-btn-hover-border-color: #2a7aaf;
--bs-btn-hover-bg: rgb(44.2, 129.2, 186.15);
--bs-btn-hover-border-color: rgb(41.6, 121.6, 175.2);
--bs-btn-focus-shadow-rgb: 82, 167, 224;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #2a7aaf;
--bs-btn-active-border-color: #2772a4;
--bs-btn-active-bg: rgb(41.6, 121.6, 175.2);
--bs-btn-active-border-color: rgb(39, 114, 164.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #3498db;
@ -3056,12 +3059,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f39c12;
--bs-btn-border-color: #f39c12;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #f5ab36;
--bs-btn-hover-border-color: #f4a62a;
--bs-btn-hover-bg: rgb(244.8, 170.85, 53.55);
--bs-btn-hover-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-focus-shadow-rgb: 207, 133, 15;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #f5b041;
--bs-btn-active-border-color: #f4a62a;
--bs-btn-active-bg: rgb(245.4, 175.8, 65.4);
--bs-btn-active-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #f39c12;
@ -3073,12 +3076,12 @@ textarea.form-control-lg {
--bs-btn-bg: #e74c3c;
--bs-btn-border-color: #e74c3c;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #c44133;
--bs-btn-hover-border-color: #b93d30;
--bs-btn-hover-bg: rgb(196.35, 64.6, 51);
--bs-btn-hover-border-color: rgb(184.8, 60.8, 48);
--bs-btn-focus-shadow-rgb: 235, 103, 89;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #b93d30;
--bs-btn-active-border-color: #ad392d;
--bs-btn-active-bg: rgb(184.8, 60.8, 48);
--bs-btn-active-border-color: rgb(173.25, 57, 45);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #e74c3c;
@ -3090,11 +3093,11 @@ textarea.form-control-lg {
--bs-btn-bg: #303030;
--bs-btn-border-color: #303030;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #292929;
--bs-btn-hover-border-color: #262626;
--bs-btn-hover-bg: rgb(40.8, 40.8, 40.8);
--bs-btn-hover-border-color: rgb(38.4, 38.4, 38.4);
--bs-btn-focus-shadow-rgb: 79, 79, 79;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #262626;
--bs-btn-active-bg: rgb(38.4, 38.4, 38.4);
--bs-btn-active-border-color: #242424;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
@ -3107,12 +3110,12 @@ textarea.form-control-lg {
--bs-btn-bg: #dee2e6;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #e3e6ea;
--bs-btn-hover-border-color: #e1e5e9;
--bs-btn-hover-bg: rgb(226.95, 230.35, 233.75);
--bs-btn-hover-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-focus-shadow-rgb: 189, 192, 196;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #e5e8eb;
--bs-btn-active-border-color: #e1e5e9;
--bs-btn-active-bg: rgb(228.6, 231.8, 235);
--bs-btn-active-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #dee2e6;
@ -4499,12 +4502,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23dee2e6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23004b38'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #80dec6;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%280, 75.2, 56%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4562,7 +4564,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4580,7 +4581,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4591,11 +4592,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4604,27 +4605,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2366d7ba'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2366d7ba'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28102, 214.8, 186%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28102, 214.8, 186%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -4669,16 +4670,16 @@ textarea.form-control-lg {
--bs-pagination-border-color: transparent;
--bs-pagination-border-radius: var(--bs-border-radius);
--bs-pagination-hover-color: #fff;
--bs-pagination-hover-bg: #00efb2;
--bs-pagination-hover-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-hover-border-color: transparent;
--bs-pagination-focus-color: var(--bs-link-hover-color);
--bs-pagination-focus-bg: var(--bs-secondary-bg);
--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
--bs-pagination-active-color: #fff;
--bs-pagination-active-bg: #00efb2;
--bs-pagination-active-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-active-border-color: transparent;
--bs-pagination-disabled-color: #fff;
--bs-pagination-disabled-bg: #007053;
--bs-pagination-disabled-bg: rgb(0, 111.5, 83.0319148936);
--bs-pagination-disabled-border-color: transparent;
display: flex;
padding-left: 0;
@ -5498,7 +5499,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6063,20 +6063,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6696,14 +6688,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -45,28 +45,28 @@
--bs-danger-rgb: 231, 76, 60;
--bs-light-rgb: 17, 17, 17;
--bs-dark-rgb: 222, 226, 230;
--bs-primary-text-emphasis: #004b38;
--bs-secondary-text-emphasis: #292929;
--bs-success-text-emphasis: #004b38;
--bs-info-text-emphasis: #153d58;
--bs-warning-text-emphasis: #613e07;
--bs-danger-text-emphasis: #5c1e18;
--bs-primary-text-emphasis: rgb(0, 75.2, 56);
--bs-secondary-text-emphasis: rgb(40.8, 40.8, 40.8);
--bs-success-text-emphasis: rgb(0, 75.2, 56);
--bs-info-text-emphasis: rgb(20.8, 60.8, 87.6);
--bs-warning-text-emphasis: rgb(97.2, 62.4, 7.2);
--bs-danger-text-emphasis: rgb(92.4, 30.4, 24);
--bs-light-text-emphasis: #333;
--bs-dark-text-emphasis: #333;
--bs-primary-bg-subtle: #ccf2e8;
--bs-secondary-bg-subtle: #e0e0e0;
--bs-success-bg-subtle: #ccf2e8;
--bs-info-bg-subtle: #d6eaf8;
--bs-warning-bg-subtle: #fdebd0;
--bs-danger-bg-subtle: #fadbd8;
--bs-light-bg-subtle: #f6f6f7;
--bs-primary-bg-subtle: rgb(204, 241.6, 232);
--bs-secondary-bg-subtle: rgb(224.4, 224.4, 224.4);
--bs-success-bg-subtle: rgb(204, 241.6, 232);
--bs-info-bg-subtle: rgb(214.4, 234.4, 247.8);
--bs-warning-bg-subtle: rgb(252.6, 235.2, 207.6);
--bs-danger-bg-subtle: rgb(250.2, 219.2, 216);
--bs-light-bg-subtle: rgb(245.5, 246, 246.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #99e4d1;
--bs-secondary-border-subtle: #c2c2c2;
--bs-success-border-subtle: #99e4d1;
--bs-info-border-subtle: #aed6f1;
--bs-warning-border-subtle: #fad7a0;
--bs-danger-border-subtle: #f5b7b1;
--bs-primary-border-subtle: rgb(153, 228.2, 209);
--bs-secondary-border-subtle: rgb(193.8, 193.8, 193.8);
--bs-success-border-subtle: rgb(153, 228.2, 209);
--bs-info-border-subtle: rgb(173.8, 213.8, 240.6);
--bs-warning-border-subtle: rgb(250.2, 215.4, 160.2);
--bs-danger-border-subtle: rgb(245.4, 183.4, 177);
--bs-light-border-subtle: #ebebeb;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 243, 243, 243;
@ -97,7 +97,7 @@
--bs-link-color: #00bc8c;
--bs-link-color-rgb: 0, 188, 140;
--bs-link-decoration: none;
--bs-link-hover-color: #009670;
--bs-link-hover-color: rgb(0, 150.4, 112);
--bs-link-hover-color-rgb: 0, 150, 112;
--bs-code-color: #d63384;
--bs-highlight-color: #ebebeb;
@ -140,46 +140,46 @@
--bs-secondary-bg-rgb: 32, 32, 32;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #191919;
--bs-tertiary-bg: rgb(24.5, 24.5, 24.5);
--bs-tertiary-bg-rgb: 25, 25, 25;
--bs-primary-text-emphasis: #66d7ba;
--bs-secondary-text-emphasis: #a3a3a3;
--bs-success-text-emphasis: #66d7ba;
--bs-info-text-emphasis: #85c1e9;
--bs-warning-text-emphasis: #f8c471;
--bs-danger-text-emphasis: #f1948a;
--bs-primary-text-emphasis: rgb(102, 214.8, 186);
--bs-secondary-text-emphasis: rgb(163.2, 163.2, 163.2);
--bs-success-text-emphasis: rgb(102, 214.8, 186);
--bs-info-text-emphasis: rgb(133.2, 193.2, 233.4);
--bs-warning-text-emphasis: rgb(247.8, 195.6, 112.8);
--bs-danger-text-emphasis: rgb(240.6, 147.6, 138);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #00261c;
--bs-secondary-bg-subtle: #141414;
--bs-success-bg-subtle: #00261c;
--bs-info-bg-subtle: #0a1e2c;
--bs-warning-bg-subtle: #311f04;
--bs-danger-bg-subtle: #2e0f0c;
--bs-primary-bg-subtle: rgb(0, 37.6, 28);
--bs-secondary-bg-subtle: rgb(20.4, 20.4, 20.4);
--bs-success-bg-subtle: rgb(0, 37.6, 28);
--bs-info-bg-subtle: rgb(10.4, 30.4, 43.8);
--bs-warning-bg-subtle: rgb(48.6, 31.2, 3.6);
--bs-danger-bg-subtle: rgb(46.2, 15.2, 12);
--bs-light-bg-subtle: #202020;
--bs-dark-bg-subtle: #101010;
--bs-primary-border-subtle: #007154;
--bs-secondary-border-subtle: #3d3d3d;
--bs-success-border-subtle: #007154;
--bs-info-border-subtle: #1f5b83;
--bs-warning-border-subtle: #925e0b;
--bs-danger-border-subtle: #8b2e24;
--bs-primary-border-subtle: rgb(0, 112.8, 84);
--bs-secondary-border-subtle: rgb(61.2, 61.2, 61.2);
--bs-success-border-subtle: rgb(0, 112.8, 84);
--bs-info-border-subtle: rgb(31.2, 91.2, 131.4);
--bs-warning-border-subtle: rgb(145.8, 93.6, 10.8);
--bs-danger-border-subtle: rgb(138.6, 45.6, 36);
--bs-light-border-subtle: #333;
--bs-dark-border-subtle: #202020;
--bs-heading-color: inherit;
--bs-link-color: #66d7ba;
--bs-link-hover-color: #85dfc8;
--bs-link-color: rgb(102, 214.8, 186);
--bs-link-hover-color: rgb(132.6, 222.84, 199.8);
--bs-link-color-rgb: 102, 215, 186;
--bs-link-hover-color-rgb: 133, 223, 200;
--bs-code-color: #e685b5;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #111;
--bs-border-color: #333;
--bs-border-color-translucent: rgba(243, 243, 243, 0.15);
--bs-form-valid-color: #66d7ba;
--bs-form-valid-border-color: #66d7ba;
--bs-form-invalid-color: #f1948a;
--bs-form-invalid-border-color: #f1948a;
--bs-form-valid-color: rgb(102, 214.8, 186);
--bs-form-valid-border-color: rgb(102, 214.8, 186);
--bs-form-invalid-color: rgb(240.6, 147.6, 138);
--bs-form-invalid-border-color: rgb(240.6, 147.6, 138);
}
*,
@ -1935,13 +1935,13 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1949,13 +1949,13 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #e0e0e0;
--bs-table-border-color: #b3b3b3;
--bs-table-striped-bg: #d5d5d5;
--bs-table-bg: rgb(224.4, 224.4, 224.4);
--bs-table-border-color: rgb(179.52, 179.52, 179.52);
--bs-table-striped-bg: rgb(213.18, 213.18, 213.18);
--bs-table-striped-color: #000;
--bs-table-active-bg: #cacaca;
--bs-table-active-bg: rgb(201.96, 201.96, 201.96);
--bs-table-active-color: #000;
--bs-table-hover-bg: #cfcfcf;
--bs-table-hover-bg: rgb(207.57, 207.57, 207.57);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1963,13 +1963,13 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1977,13 +1977,13 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #d6eaf8;
--bs-table-border-color: #abbbc6;
--bs-table-striped-bg: #cbdeec;
--bs-table-bg: rgb(214.4, 234.4, 247.8);
--bs-table-border-color: rgb(171.52, 187.52, 198.24);
--bs-table-striped-bg: rgb(203.68, 222.68, 235.41);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c1d3df;
--bs-table-active-bg: rgb(192.96, 210.96, 223.02);
--bs-table-active-color: #000;
--bs-table-hover-bg: #c6d8e5;
--bs-table-hover-bg: rgb(198.32, 216.82, 229.215);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1991,13 +1991,13 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fdebd0;
--bs-table-border-color: #cabca6;
--bs-table-striped-bg: #f0dfc6;
--bs-table-bg: rgb(252.6, 235.2, 207.6);
--bs-table-border-color: rgb(202.08, 188.16, 166.08);
--bs-table-striped-bg: rgb(239.97, 223.44, 197.22);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e4d4bb;
--bs-table-active-bg: rgb(227.34, 211.68, 186.84);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ead9c0;
--bs-table-hover-bg: rgb(233.655, 217.56, 192.03);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2005,13 +2005,13 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #fadbd8;
--bs-table-border-color: #c8afad;
--bs-table-striped-bg: #eed0cd;
--bs-table-bg: rgb(250.2, 219.2, 216);
--bs-table-border-color: rgb(200.16, 175.36, 172.8);
--bs-table-striped-bg: rgb(237.69, 208.24, 205.2);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e1c5c2;
--bs-table-active-bg: rgb(225.18, 197.28, 194.4);
--bs-table-active-color: #000;
--bs-table-hover-bg: #e7cbc8;
--bs-table-hover-bg: rgb(231.435, 202.76, 199.8);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2020,12 +2020,12 @@ progress {
.table-light {
--bs-table-color: #f3f3f3;
--bs-table-bg: #111;
--bs-table-border-color: #3e3e3e;
--bs-table-striped-bg: #1c1c1c;
--bs-table-border-color: rgb(62.2, 62.2, 62.2);
--bs-table-striped-bg: rgb(28.3, 28.3, 28.3);
--bs-table-striped-color: #f3f3f3;
--bs-table-active-bg: #282828;
--bs-table-active-bg: rgb(39.6, 39.6, 39.6);
--bs-table-active-color: #f3f3f3;
--bs-table-hover-bg: #222222;
--bs-table-hover-bg: rgb(33.95, 33.95, 33.95);
--bs-table-hover-color: #f3f3f3;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2034,12 +2034,12 @@ progress {
.table-dark {
--bs-table-color: #000;
--bs-table-bg: #dee2e6;
--bs-table-border-color: #b2b5b8;
--bs-table-striped-bg: #d3d7db;
--bs-table-border-color: rgb(177.6, 180.8, 184);
--bs-table-striped-bg: rgb(210.9, 214.7, 218.5);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c8cbcf;
--bs-table-active-bg: rgb(199.8, 203.4, 207);
--bs-table-active-color: #000;
--bs-table-hover-bg: #cdd1d5;
--bs-table-hover-bg: rgb(205.35, 209.05, 212.75);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2139,7 +2139,7 @@ progress {
.form-control:focus {
color: #f3f3f3;
background-color: #111;
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2284,7 +2284,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2368,7 +2368,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2415,7 +2415,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2380dec6'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28127.5, 221.5, 197.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2485,7 +2485,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #b3ebdd;
background-color: rgb(178.5, 234.9, 220.5);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2511,7 +2511,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #b3ebdd;
background-color: rgb(178.5, 234.9, 220.5);
}
.form-range::-moz-range-track {
width: 100%;
@ -2959,6 +2959,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2972,12 +2975,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -2989,12 +2992,12 @@ textarea.form-control-lg {
--bs-btn-bg: #666;
--bs-btn-border-color: #666;
--bs-btn-hover-color: #f3f3f3;
--bs-btn-hover-bg: #575757;
--bs-btn-hover-border-color: #525252;
--bs-btn-hover-bg: rgb(86.7, 86.7, 86.7);
--bs-btn-hover-border-color: rgb(81.6, 81.6, 81.6);
--bs-btn-focus-shadow-rgb: 123, 123, 123;
--bs-btn-active-color: #f3f3f3;
--bs-btn-active-bg: #525252;
--bs-btn-active-border-color: #4d4d4d;
--bs-btn-active-bg: rgb(81.6, 81.6, 81.6);
--bs-btn-active-border-color: rgb(76.5, 76.5, 76.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #f3f3f3;
--bs-btn-disabled-bg: #666;
@ -3006,12 +3009,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -3023,12 +3026,12 @@ textarea.form-control-lg {
--bs-btn-bg: #3498db;
--bs-btn-border-color: #3498db;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #52a7e0;
--bs-btn-hover-border-color: #48a2df;
--bs-btn-hover-bg: rgb(82.45, 167.45, 224.4);
--bs-btn-hover-border-color: rgb(72.3, 162.3, 222.6);
--bs-btn-focus-shadow-rgb: 44, 129, 186;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #5dade2;
--bs-btn-active-border-color: #48a2df;
--bs-btn-active-bg: rgb(92.6, 172.6, 226.2);
--bs-btn-active-border-color: rgb(72.3, 162.3, 222.6);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #3498db;
@ -3040,12 +3043,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f39c12;
--bs-btn-border-color: #f39c12;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #f5ab36;
--bs-btn-hover-border-color: #f4a62a;
--bs-btn-hover-bg: rgb(244.8, 170.85, 53.55);
--bs-btn-hover-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-focus-shadow-rgb: 207, 133, 15;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #f5b041;
--bs-btn-active-border-color: #f4a62a;
--bs-btn-active-bg: rgb(245.4, 175.8, 65.4);
--bs-btn-active-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #f39c12;
@ -3057,12 +3060,12 @@ textarea.form-control-lg {
--bs-btn-bg: #e74c3c;
--bs-btn-border-color: #e74c3c;
--bs-btn-hover-color: #f3f3f3;
--bs-btn-hover-bg: #c44133;
--bs-btn-hover-border-color: #b93d30;
--bs-btn-hover-bg: rgb(196.35, 64.6, 51);
--bs-btn-hover-border-color: rgb(184.8, 60.8, 48);
--bs-btn-focus-shadow-rgb: 233, 101, 87;
--bs-btn-active-color: #f3f3f3;
--bs-btn-active-bg: #b93d30;
--bs-btn-active-border-color: #ad392d;
--bs-btn-active-bg: rgb(184.8, 60.8, 48);
--bs-btn-active-border-color: rgb(173.25, 57, 45);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #f3f3f3;
--bs-btn-disabled-bg: #e74c3c;
@ -3074,12 +3077,12 @@ textarea.form-control-lg {
--bs-btn-bg: #111;
--bs-btn-border-color: #111;
--bs-btn-hover-color: #f3f3f3;
--bs-btn-hover-bg: #0e0e0e;
--bs-btn-hover-border-color: #0e0e0e;
--bs-btn-hover-bg: rgb(14.45, 14.45, 14.45);
--bs-btn-hover-border-color: rgb(13.6, 13.6, 13.6);
--bs-btn-focus-shadow-rgb: 51, 51, 51;
--bs-btn-active-color: #f3f3f3;
--bs-btn-active-bg: #0e0e0e;
--bs-btn-active-border-color: #0d0d0d;
--bs-btn-active-bg: rgb(13.6, 13.6, 13.6);
--bs-btn-active-border-color: rgb(12.75, 12.75, 12.75);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #f3f3f3;
--bs-btn-disabled-bg: #111;
@ -3091,12 +3094,12 @@ textarea.form-control-lg {
--bs-btn-bg: #dee2e6;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #e3e6ea;
--bs-btn-hover-border-color: #e1e5e9;
--bs-btn-hover-bg: rgb(226.95, 230.35, 233.75);
--bs-btn-hover-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-focus-shadow-rgb: 189, 192, 196;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #e5e8eb;
--bs-btn-active-border-color: #e1e5e9;
--bs-btn-active-bg: rgb(228.6, 231.8, 235);
--bs-btn-active-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #dee2e6;
@ -4487,12 +4490,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ebebeb'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ebebeb' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23004b38'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #80dec6;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%280, 75.2, 56%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4550,7 +4552,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4568,7 +4569,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4579,11 +4580,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4592,27 +4593,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2366d7ba'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2366d7ba'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28102, 214.8, 186%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28102, 214.8, 186%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -4657,16 +4658,16 @@ textarea.form-control-lg {
--bs-pagination-border-color: transparent;
--bs-pagination-border-radius: var(--bs-border-radius);
--bs-pagination-hover-color: #f3f3f3;
--bs-pagination-hover-bg: #00efb2;
--bs-pagination-hover-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-hover-border-color: transparent;
--bs-pagination-focus-color: var(--bs-link-hover-color);
--bs-pagination-focus-bg: var(--bs-secondary-bg);
--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
--bs-pagination-active-color: #f3f3f3;
--bs-pagination-active-bg: #00efb2;
--bs-pagination-active-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-active-border-color: transparent;
--bs-pagination-disabled-color: #f3f3f3;
--bs-pagination-disabled-bg: #007053;
--bs-pagination-disabled-bg: rgb(0, 111.5, 83.0319148936);
--bs-pagination-disabled-border-color: transparent;
display: flex;
padding-left: 0;
@ -5486,7 +5487,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6051,20 +6051,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f3f3f3'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f3f3f3'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f3f3f3'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f3f3f3'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f3f3f3'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f3f3f3'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6684,14 +6676,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -45,28 +45,28 @@
--bs-danger-rgb: 231, 76, 60;
--bs-light-rgb: 48, 48, 48;
--bs-dark-rgb: 222, 226, 230;
--bs-primary-text-emphasis: #162433;
--bs-secondary-text-emphasis: #45484c;
--bs-success-text-emphasis: #004b38;
--bs-info-text-emphasis: #153d58;
--bs-warning-text-emphasis: #613e07;
--bs-danger-text-emphasis: #5c1e18;
--bs-primary-text-emphasis: rgb(22, 36, 50.8);
--bs-secondary-text-emphasis: rgb(69.2, 72.4, 75.6);
--bs-success-text-emphasis: rgb(0, 75.2, 56);
--bs-info-text-emphasis: rgb(20.8, 60.8, 87.6);
--bs-warning-text-emphasis: rgb(97.2, 62.4, 7.2);
--bs-danger-text-emphasis: rgb(92.4, 30.4, 24);
--bs-light-text-emphasis: #444;
--bs-dark-text-emphasis: #444;
--bs-primary-bg-subtle: #d7dee5;
--bs-secondary-bg-subtle: #eff0f2;
--bs-success-bg-subtle: #ccf2e8;
--bs-info-bg-subtle: #d6eaf8;
--bs-warning-bg-subtle: #fdebd0;
--bs-danger-bg-subtle: #fadbd8;
--bs-light-bg-subtle: #fcfcfd;
--bs-primary-bg-subtle: rgb(215, 222, 229.4);
--bs-secondary-bg-subtle: rgb(238.6, 240.2, 241.8);
--bs-success-bg-subtle: rgb(204, 241.6, 232);
--bs-info-bg-subtle: rgb(214.4, 234.4, 247.8);
--bs-warning-bg-subtle: rgb(252.6, 235.2, 207.6);
--bs-danger-bg-subtle: rgb(250.2, 219.2, 216);
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #afbdcc;
--bs-secondary-border-subtle: #dee1e5;
--bs-success-border-subtle: #99e4d1;
--bs-info-border-subtle: #aed6f1;
--bs-warning-border-subtle: #fad7a0;
--bs-danger-border-subtle: #f5b7b1;
--bs-primary-border-subtle: rgb(175, 189, 203.8);
--bs-secondary-border-subtle: rgb(222.2, 225.4, 228.6);
--bs-success-border-subtle: rgb(153, 228.2, 209);
--bs-info-border-subtle: rgb(173.8, 213.8, 240.6);
--bs-warning-border-subtle: rgb(250.2, 215.4, 160.2);
--bs-danger-border-subtle: rgb(245.4, 183.4, 177);
--bs-light-border-subtle: #ebebeb;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
@ -97,7 +97,7 @@
--bs-link-color: #e74c3c;
--bs-link-color-rgb: 231, 76, 60;
--bs-link-decoration: none;
--bs-link-hover-color: #b93d30;
--bs-link-hover-color: rgb(184.8, 60.8, 48);
--bs-link-hover-color-rgb: 185, 61, 48;
--bs-code-color: #d63384;
--bs-highlight-color: #dee2e6;
@ -142,44 +142,44 @@
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #292929;
--bs-tertiary-bg-rgb: 41, 41, 41;
--bs-primary-text-emphasis: #879cb2;
--bs-secondary-text-emphasis: #ced3d7;
--bs-success-text-emphasis: #66d7ba;
--bs-info-text-emphasis: #85c1e9;
--bs-warning-text-emphasis: #f8c471;
--bs-danger-text-emphasis: #f1948a;
--bs-primary-text-emphasis: rgb(135, 156, 178.2);
--bs-secondary-text-emphasis: rgb(205.8, 210.6, 215.4);
--bs-success-text-emphasis: rgb(102, 214.8, 186);
--bs-info-text-emphasis: rgb(133.2, 193.2, 233.4);
--bs-warning-text-emphasis: rgb(247.8, 195.6, 112.8);
--bs-danger-text-emphasis: rgb(240.6, 147.6, 138);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #0b1219;
--bs-secondary-bg-subtle: #232426;
--bs-success-bg-subtle: #00261c;
--bs-info-bg-subtle: #0a1e2c;
--bs-warning-bg-subtle: #311f04;
--bs-danger-bg-subtle: #2e0f0c;
--bs-primary-bg-subtle: rgb(11, 18, 25.4);
--bs-secondary-bg-subtle: rgb(34.6, 36.2, 37.8);
--bs-success-bg-subtle: rgb(0, 37.6, 28);
--bs-info-bg-subtle: rgb(10.4, 30.4, 43.8);
--bs-warning-bg-subtle: rgb(48.6, 31.2, 3.6);
--bs-danger-bg-subtle: rgb(46.2, 15.2, 12);
--bs-light-bg-subtle: #303030;
--bs-dark-bg-subtle: #181818;
--bs-primary-border-subtle: #21364c;
--bs-secondary-border-subtle: #686d71;
--bs-success-border-subtle: #007154;
--bs-info-border-subtle: #1f5b83;
--bs-warning-border-subtle: #925e0b;
--bs-danger-border-subtle: #8b2e24;
--bs-primary-border-subtle: rgb(33, 54, 76.2);
--bs-secondary-border-subtle: rgb(103.8, 108.6, 113.4);
--bs-success-border-subtle: rgb(0, 112.8, 84);
--bs-info-border-subtle: rgb(31.2, 91.2, 131.4);
--bs-warning-border-subtle: rgb(145.8, 93.6, 10.8);
--bs-danger-border-subtle: rgb(138.6, 45.6, 36);
--bs-light-border-subtle: #444;
--bs-dark-border-subtle: #303030;
--bs-heading-color: inherit;
--bs-link-color: #879cb2;
--bs-link-hover-color: #9fb0c1;
--bs-link-color: rgb(135, 156, 178.2);
--bs-link-hover-color: rgb(159, 175.8, 193.56);
--bs-link-color-rgb: 135, 156, 178;
--bs-link-hover-color-rgb: 159, 176, 193;
--bs-code-color: #e685b5;
--bs-link-hover-color-rgb: 159, 176, 194;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #333;
--bs-border-color: #444;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #66d7ba;
--bs-form-valid-border-color: #66d7ba;
--bs-form-invalid-color: #f1948a;
--bs-form-invalid-border-color: #f1948a;
--bs-form-valid-color: rgb(102, 214.8, 186);
--bs-form-valid-border-color: rgb(102, 214.8, 186);
--bs-form-invalid-color: rgb(240.6, 147.6, 138);
--bs-form-invalid-border-color: rgb(240.6, 147.6, 138);
}
*,
@ -1935,13 +1935,13 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #d7dee5;
--bs-table-border-color: #acb2b7;
--bs-table-striped-bg: #ccd3da;
--bs-table-bg: rgb(215, 222, 229.4);
--bs-table-border-color: rgb(172, 177.6, 183.52);
--bs-table-striped-bg: rgb(204.25, 210.9, 217.93);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c2c8ce;
--bs-table-active-bg: rgb(193.5, 199.8, 206.46);
--bs-table-active-color: #000;
--bs-table-hover-bg: #c7cdd4;
--bs-table-hover-bg: rgb(198.875, 205.35, 212.195);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1949,13 +1949,13 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #eff0f2;
--bs-table-border-color: #bfc0c2;
--bs-table-striped-bg: #e3e4e6;
--bs-table-bg: rgb(238.6, 240.2, 241.8);
--bs-table-border-color: rgb(190.88, 192.16, 193.44);
--bs-table-striped-bg: rgb(226.67, 228.19, 229.71);
--bs-table-striped-color: #000;
--bs-table-active-bg: #d7d8da;
--bs-table-active-bg: rgb(214.74, 216.18, 217.62);
--bs-table-active-color: #000;
--bs-table-hover-bg: #dddee0;
--bs-table-hover-bg: rgb(220.705, 222.185, 223.665);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1963,13 +1963,13 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1977,13 +1977,13 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #d6eaf8;
--bs-table-border-color: #abbbc6;
--bs-table-striped-bg: #cbdeec;
--bs-table-bg: rgb(214.4, 234.4, 247.8);
--bs-table-border-color: rgb(171.52, 187.52, 198.24);
--bs-table-striped-bg: rgb(203.68, 222.68, 235.41);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c1d3df;
--bs-table-active-bg: rgb(192.96, 210.96, 223.02);
--bs-table-active-color: #000;
--bs-table-hover-bg: #c6d8e5;
--bs-table-hover-bg: rgb(198.32, 216.82, 229.215);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1991,13 +1991,13 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fdebd0;
--bs-table-border-color: #cabca6;
--bs-table-striped-bg: #f0dfc6;
--bs-table-bg: rgb(252.6, 235.2, 207.6);
--bs-table-border-color: rgb(202.08, 188.16, 166.08);
--bs-table-striped-bg: rgb(239.97, 223.44, 197.22);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e4d4bb;
--bs-table-active-bg: rgb(227.34, 211.68, 186.84);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ead9c0;
--bs-table-hover-bg: rgb(233.655, 217.56, 192.03);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2005,13 +2005,13 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #fadbd8;
--bs-table-border-color: #c8afad;
--bs-table-striped-bg: #eed0cd;
--bs-table-bg: rgb(250.2, 219.2, 216);
--bs-table-border-color: rgb(200.16, 175.36, 172.8);
--bs-table-striped-bg: rgb(237.69, 208.24, 205.2);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e1c5c2;
--bs-table-active-bg: rgb(225.18, 197.28, 194.4);
--bs-table-active-color: #000;
--bs-table-hover-bg: #e7cbc8;
--bs-table-hover-bg: rgb(231.435, 202.76, 199.8);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2020,12 +2020,12 @@ progress {
.table-light {
--bs-table-color: #fff;
--bs-table-bg: #303030;
--bs-table-border-color: #595959;
--bs-table-striped-bg: #3a3a3a;
--bs-table-border-color: rgb(89.4, 89.4, 89.4);
--bs-table-striped-bg: rgb(58.35, 58.35, 58.35);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #454545;
--bs-table-active-bg: rgb(68.7, 68.7, 68.7);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #404040;
--bs-table-hover-bg: rgb(63.525, 63.525, 63.525);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2034,12 +2034,12 @@ progress {
.table-dark {
--bs-table-color: #000;
--bs-table-bg: #dee2e6;
--bs-table-border-color: #b2b5b8;
--bs-table-striped-bg: #d3d7db;
--bs-table-border-color: rgb(177.6, 180.8, 184);
--bs-table-striped-bg: rgb(210.9, 214.7, 218.5);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c8cbcf;
--bs-table-active-bg: rgb(199.8, 203.4, 207);
--bs-table-active-color: #000;
--bs-table-hover-bg: #cdd1d5;
--bs-table-hover-bg: rgb(205.35, 209.05, 212.75);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2139,7 +2139,7 @@ progress {
.form-control:focus {
color: #fff;
background-color: #444;
border-color: #9badbf;
border-color: rgb(155, 172.5, 191);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);
}
@ -2157,7 +2157,7 @@ progress {
opacity: 1;
}
.form-control:disabled {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
opacity: 1;
}
.form-control::file-selector-button {
@ -2284,7 +2284,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #9badbf;
border-color: rgb(155, 172.5, 191);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);
}
@ -2293,7 +2293,7 @@ textarea.form-control-lg {
background-image: none;
}
.form-select:disabled {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
}
.form-select:-moz-focusring {
color: transparent;
@ -2368,7 +2368,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #9badbf;
border-color: rgb(155, 172.5, 191);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);
}
@ -2415,7 +2415,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%239badbf'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28155, 172.5, 191%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2485,7 +2485,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #c3ced9;
background-color: rgb(195, 205.5, 216.6);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2511,7 +2511,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #c3ced9;
background-color: rgb(195, 205.5, 216.6);
}
.form-range::-moz-range-track {
width: 100%;
@ -2618,7 +2618,7 @@ textarea.form-control-lg {
}
.form-floating > :disabled ~ label::after,
.form-floating > .form-control:disabled ~ label::after {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
}
.input-group {
@ -2959,6 +2959,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2972,12 +2975,12 @@ textarea.form-control-lg {
--bs-btn-bg: #375a7f;
--bs-btn-border-color: #375a7f;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #2f4d6c;
--bs-btn-hover-border-color: #2c4866;
--bs-btn-hover-bg: rgb(46.75, 76.5, 107.95);
--bs-btn-hover-border-color: rgb(44, 72, 101.6);
--bs-btn-focus-shadow-rgb: 85, 115, 146;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #2c4866;
--bs-btn-active-border-color: #29445f;
--bs-btn-active-bg: rgb(44, 72, 101.6);
--bs-btn-active-border-color: rgb(41.25, 67.5, 95.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #375a7f;
@ -2989,12 +2992,12 @@ textarea.form-control-lg {
--bs-btn-bg: #adb5bd;
--bs-btn-border-color: #adb5bd;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #b9c0c7;
--bs-btn-hover-border-color: #b5bcc4;
--bs-btn-hover-bg: rgb(185.3, 192.1, 198.9);
--bs-btn-hover-border-color: rgb(181.2, 188.4, 195.6);
--bs-btn-focus-shadow-rgb: 147, 154, 161;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #bdc4ca;
--bs-btn-active-border-color: #b5bcc4;
--bs-btn-active-bg: rgb(189.4, 195.8, 202.2);
--bs-btn-active-border-color: rgb(181.2, 188.4, 195.6);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #adb5bd;
@ -3006,12 +3009,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -3023,12 +3026,12 @@ textarea.form-control-lg {
--bs-btn-bg: #3498db;
--bs-btn-border-color: #3498db;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #2c81ba;
--bs-btn-hover-border-color: #2a7aaf;
--bs-btn-hover-bg: rgb(44.2, 129.2, 186.15);
--bs-btn-hover-border-color: rgb(41.6, 121.6, 175.2);
--bs-btn-focus-shadow-rgb: 82, 167, 224;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #2a7aaf;
--bs-btn-active-border-color: #2772a4;
--bs-btn-active-bg: rgb(41.6, 121.6, 175.2);
--bs-btn-active-border-color: rgb(39, 114, 164.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #3498db;
@ -3040,12 +3043,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f39c12;
--bs-btn-border-color: #f39c12;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #f5ab36;
--bs-btn-hover-border-color: #f4a62a;
--bs-btn-hover-bg: rgb(244.8, 170.85, 53.55);
--bs-btn-hover-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-focus-shadow-rgb: 207, 133, 15;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #f5b041;
--bs-btn-active-border-color: #f4a62a;
--bs-btn-active-bg: rgb(245.4, 175.8, 65.4);
--bs-btn-active-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #f39c12;
@ -3057,12 +3060,12 @@ textarea.form-control-lg {
--bs-btn-bg: #e74c3c;
--bs-btn-border-color: #e74c3c;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #c44133;
--bs-btn-hover-border-color: #b93d30;
--bs-btn-hover-bg: rgb(196.35, 64.6, 51);
--bs-btn-hover-border-color: rgb(184.8, 60.8, 48);
--bs-btn-focus-shadow-rgb: 235, 103, 89;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #b93d30;
--bs-btn-active-border-color: #ad392d;
--bs-btn-active-bg: rgb(184.8, 60.8, 48);
--bs-btn-active-border-color: rgb(173.25, 57, 45);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #e74c3c;
@ -3074,11 +3077,11 @@ textarea.form-control-lg {
--bs-btn-bg: #303030;
--bs-btn-border-color: #303030;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #292929;
--bs-btn-hover-border-color: #262626;
--bs-btn-hover-bg: rgb(40.8, 40.8, 40.8);
--bs-btn-hover-border-color: rgb(38.4, 38.4, 38.4);
--bs-btn-focus-shadow-rgb: 79, 79, 79;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #262626;
--bs-btn-active-bg: rgb(38.4, 38.4, 38.4);
--bs-btn-active-border-color: #242424;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
@ -3091,12 +3094,12 @@ textarea.form-control-lg {
--bs-btn-bg: #dee2e6;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #e3e6ea;
--bs-btn-hover-border-color: #e1e5e9;
--bs-btn-hover-bg: rgb(226.95, 230.35, 233.75);
--bs-btn-hover-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-focus-shadow-rgb: 189, 192, 196;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #e5e8eb;
--bs-btn-active-border-color: #e1e5e9;
--bs-btn-active-bg: rgb(228.6, 231.8, 235);
--bs-btn-active-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #dee2e6;
@ -4487,12 +4490,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23dee2e6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23162433'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #9badbf;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%2822, 36, 50.8%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4550,7 +4552,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4568,7 +4569,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4579,11 +4580,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4592,27 +4593,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23879cb2'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23879cb2'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28135, 156, 178.2%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28135, 156, 178.2%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -4657,16 +4658,16 @@ textarea.form-control-lg {
--bs-pagination-border-color: transparent;
--bs-pagination-border-radius: var(--bs-border-radius);
--bs-pagination-hover-color: #fff;
--bs-pagination-hover-bg: #00efb2;
--bs-pagination-hover-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-hover-border-color: transparent;
--bs-pagination-focus-color: var(--bs-link-hover-color);
--bs-pagination-focus-bg: var(--bs-secondary-bg);
--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(55, 90, 127, 0.25);
--bs-pagination-active-color: #fff;
--bs-pagination-active-bg: #00efb2;
--bs-pagination-active-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-active-border-color: transparent;
--bs-pagination-disabled-color: #fff;
--bs-pagination-disabled-bg: #007053;
--bs-pagination-disabled-bg: rgb(0, 111.5, 83.0319148936);
--bs-pagination-disabled-border-color: transparent;
display: flex;
padding-left: 0;
@ -5486,7 +5487,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6051,20 +6051,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6684,14 +6676,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -45,28 +45,28 @@
--bs-danger-rgb: 231, 76, 60;
--bs-light-rgb: 48, 48, 48;
--bs-dark-rgb: 222, 226, 230;
--bs-primary-text-emphasis: #004b38;
--bs-secondary-text-emphasis: #45484c;
--bs-success-text-emphasis: #004b38;
--bs-info-text-emphasis: #153d58;
--bs-warning-text-emphasis: #613e07;
--bs-danger-text-emphasis: #5c1e18;
--bs-primary-text-emphasis: rgb(0, 75.2, 56);
--bs-secondary-text-emphasis: rgb(69.2, 72.4, 75.6);
--bs-success-text-emphasis: rgb(0, 75.2, 56);
--bs-info-text-emphasis: rgb(20.8, 60.8, 87.6);
--bs-warning-text-emphasis: rgb(97.2, 62.4, 7.2);
--bs-danger-text-emphasis: rgb(92.4, 30.4, 24);
--bs-light-text-emphasis: #444;
--bs-dark-text-emphasis: #444;
--bs-primary-bg-subtle: #ccf2e8;
--bs-secondary-bg-subtle: #eff0f2;
--bs-success-bg-subtle: #ccf2e8;
--bs-info-bg-subtle: #d6eaf8;
--bs-warning-bg-subtle: #fdebd0;
--bs-danger-bg-subtle: #fadbd8;
--bs-light-bg-subtle: #fcfcfd;
--bs-primary-bg-subtle: rgb(204, 241.6, 232);
--bs-secondary-bg-subtle: rgb(238.6, 240.2, 241.8);
--bs-success-bg-subtle: rgb(204, 241.6, 232);
--bs-info-bg-subtle: rgb(214.4, 234.4, 247.8);
--bs-warning-bg-subtle: rgb(252.6, 235.2, 207.6);
--bs-danger-bg-subtle: rgb(250.2, 219.2, 216);
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #99e4d1;
--bs-secondary-border-subtle: #dee1e5;
--bs-success-border-subtle: #99e4d1;
--bs-info-border-subtle: #aed6f1;
--bs-warning-border-subtle: #fad7a0;
--bs-danger-border-subtle: #f5b7b1;
--bs-primary-border-subtle: rgb(153, 228.2, 209);
--bs-secondary-border-subtle: rgb(222.2, 225.4, 228.6);
--bs-success-border-subtle: rgb(153, 228.2, 209);
--bs-info-border-subtle: rgb(173.8, 213.8, 240.6);
--bs-warning-border-subtle: rgb(250.2, 215.4, 160.2);
--bs-danger-border-subtle: rgb(245.4, 183.4, 177);
--bs-light-border-subtle: #ebebeb;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
@ -97,7 +97,7 @@
--bs-link-color: #00bc8c;
--bs-link-color-rgb: 0, 188, 140;
--bs-link-decoration: none;
--bs-link-hover-color: #009670;
--bs-link-hover-color: rgb(0, 150.4, 112);
--bs-link-hover-color-rgb: 0, 150, 112;
--bs-code-color: #d63384;
--bs-highlight-color: #dee2e6;
@ -142,44 +142,44 @@
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #292929;
--bs-tertiary-bg-rgb: 41, 41, 41;
--bs-primary-text-emphasis: #66d7ba;
--bs-secondary-text-emphasis: #ced3d7;
--bs-success-text-emphasis: #66d7ba;
--bs-info-text-emphasis: #85c1e9;
--bs-warning-text-emphasis: #f8c471;
--bs-danger-text-emphasis: #f1948a;
--bs-primary-text-emphasis: rgb(102, 214.8, 186);
--bs-secondary-text-emphasis: rgb(205.8, 210.6, 215.4);
--bs-success-text-emphasis: rgb(102, 214.8, 186);
--bs-info-text-emphasis: rgb(133.2, 193.2, 233.4);
--bs-warning-text-emphasis: rgb(247.8, 195.6, 112.8);
--bs-danger-text-emphasis: rgb(240.6, 147.6, 138);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #00261c;
--bs-secondary-bg-subtle: #232426;
--bs-success-bg-subtle: #00261c;
--bs-info-bg-subtle: #0a1e2c;
--bs-warning-bg-subtle: #311f04;
--bs-danger-bg-subtle: #2e0f0c;
--bs-primary-bg-subtle: rgb(0, 37.6, 28);
--bs-secondary-bg-subtle: rgb(34.6, 36.2, 37.8);
--bs-success-bg-subtle: rgb(0, 37.6, 28);
--bs-info-bg-subtle: rgb(10.4, 30.4, 43.8);
--bs-warning-bg-subtle: rgb(48.6, 31.2, 3.6);
--bs-danger-bg-subtle: rgb(46.2, 15.2, 12);
--bs-light-bg-subtle: #303030;
--bs-dark-bg-subtle: #181818;
--bs-primary-border-subtle: #007154;
--bs-secondary-border-subtle: #686d71;
--bs-success-border-subtle: #007154;
--bs-info-border-subtle: #1f5b83;
--bs-warning-border-subtle: #925e0b;
--bs-danger-border-subtle: #8b2e24;
--bs-primary-border-subtle: rgb(0, 112.8, 84);
--bs-secondary-border-subtle: rgb(103.8, 108.6, 113.4);
--bs-success-border-subtle: rgb(0, 112.8, 84);
--bs-info-border-subtle: rgb(31.2, 91.2, 131.4);
--bs-warning-border-subtle: rgb(145.8, 93.6, 10.8);
--bs-danger-border-subtle: rgb(138.6, 45.6, 36);
--bs-light-border-subtle: #444;
--bs-dark-border-subtle: #303030;
--bs-heading-color: inherit;
--bs-link-color: #66d7ba;
--bs-link-hover-color: #85dfc8;
--bs-link-color: rgb(102, 214.8, 186);
--bs-link-hover-color: rgb(132.6, 222.84, 199.8);
--bs-link-color-rgb: 102, 215, 186;
--bs-link-hover-color-rgb: 133, 223, 200;
--bs-code-color: #e685b5;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #333;
--bs-border-color: #444;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #66d7ba;
--bs-form-valid-border-color: #66d7ba;
--bs-form-invalid-color: #f1948a;
--bs-form-invalid-border-color: #f1948a;
--bs-form-valid-color: rgb(102, 214.8, 186);
--bs-form-valid-border-color: rgb(102, 214.8, 186);
--bs-form-invalid-color: rgb(240.6, 147.6, 138);
--bs-form-invalid-border-color: rgb(240.6, 147.6, 138);
}
*,
@ -1935,13 +1935,13 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1949,13 +1949,13 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #eff0f2;
--bs-table-border-color: #bfc0c2;
--bs-table-striped-bg: #e3e4e6;
--bs-table-bg: rgb(238.6, 240.2, 241.8);
--bs-table-border-color: rgb(190.88, 192.16, 193.44);
--bs-table-striped-bg: rgb(226.67, 228.19, 229.71);
--bs-table-striped-color: #000;
--bs-table-active-bg: #d7d8da;
--bs-table-active-bg: rgb(214.74, 216.18, 217.62);
--bs-table-active-color: #000;
--bs-table-hover-bg: #dddee0;
--bs-table-hover-bg: rgb(220.705, 222.185, 223.665);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1963,13 +1963,13 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #ccf2e8;
--bs-table-border-color: #a3c2ba;
--bs-table-striped-bg: #c2e6dc;
--bs-table-bg: rgb(204, 241.6, 232);
--bs-table-border-color: rgb(163.2, 193.28, 185.6);
--bs-table-striped-bg: rgb(193.8, 229.52, 220.4);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dad1;
--bs-table-active-bg: rgb(183.6, 217.44, 208.8);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde0d7;
--bs-table-hover-bg: rgb(188.7, 223.48, 214.6);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1977,13 +1977,13 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #d6eaf8;
--bs-table-border-color: #abbbc6;
--bs-table-striped-bg: #cbdeec;
--bs-table-bg: rgb(214.4, 234.4, 247.8);
--bs-table-border-color: rgb(171.52, 187.52, 198.24);
--bs-table-striped-bg: rgb(203.68, 222.68, 235.41);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c1d3df;
--bs-table-active-bg: rgb(192.96, 210.96, 223.02);
--bs-table-active-color: #000;
--bs-table-hover-bg: #c6d8e5;
--bs-table-hover-bg: rgb(198.32, 216.82, 229.215);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1991,13 +1991,13 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fdebd0;
--bs-table-border-color: #cabca6;
--bs-table-striped-bg: #f0dfc6;
--bs-table-bg: rgb(252.6, 235.2, 207.6);
--bs-table-border-color: rgb(202.08, 188.16, 166.08);
--bs-table-striped-bg: rgb(239.97, 223.44, 197.22);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e4d4bb;
--bs-table-active-bg: rgb(227.34, 211.68, 186.84);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ead9c0;
--bs-table-hover-bg: rgb(233.655, 217.56, 192.03);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2005,13 +2005,13 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #fadbd8;
--bs-table-border-color: #c8afad;
--bs-table-striped-bg: #eed0cd;
--bs-table-bg: rgb(250.2, 219.2, 216);
--bs-table-border-color: rgb(200.16, 175.36, 172.8);
--bs-table-striped-bg: rgb(237.69, 208.24, 205.2);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e1c5c2;
--bs-table-active-bg: rgb(225.18, 197.28, 194.4);
--bs-table-active-color: #000;
--bs-table-hover-bg: #e7cbc8;
--bs-table-hover-bg: rgb(231.435, 202.76, 199.8);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2020,12 +2020,12 @@ progress {
.table-light {
--bs-table-color: #fff;
--bs-table-bg: #303030;
--bs-table-border-color: #595959;
--bs-table-striped-bg: #3a3a3a;
--bs-table-border-color: rgb(89.4, 89.4, 89.4);
--bs-table-striped-bg: rgb(58.35, 58.35, 58.35);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #454545;
--bs-table-active-bg: rgb(68.7, 68.7, 68.7);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #404040;
--bs-table-hover-bg: rgb(63.525, 63.525, 63.525);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2034,12 +2034,12 @@ progress {
.table-dark {
--bs-table-color: #000;
--bs-table-bg: #dee2e6;
--bs-table-border-color: #b2b5b8;
--bs-table-striped-bg: #d3d7db;
--bs-table-border-color: rgb(177.6, 180.8, 184);
--bs-table-striped-bg: rgb(210.9, 214.7, 218.5);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c8cbcf;
--bs-table-active-bg: rgb(199.8, 203.4, 207);
--bs-table-active-color: #000;
--bs-table-hover-bg: #cdd1d5;
--bs-table-hover-bg: rgb(205.35, 209.05, 212.75);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2139,7 +2139,7 @@ progress {
.form-control:focus {
color: #fff;
background-color: #444;
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2157,7 +2157,7 @@ progress {
opacity: 1;
}
.form-control:disabled {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
opacity: 1;
}
.form-control::file-selector-button {
@ -2284,7 +2284,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2293,7 +2293,7 @@ textarea.form-control-lg {
background-image: none;
}
.form-select:disabled {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
}
.form-select:-moz-focusring {
color: transparent;
@ -2368,7 +2368,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #80dec6;
border-color: rgb(127.5, 221.5, 197.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
}
@ -2415,7 +2415,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2380dec6'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28127.5, 221.5, 197.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2485,7 +2485,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #b3ebdd;
background-color: rgb(178.5, 234.9, 220.5);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2511,7 +2511,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #b3ebdd;
background-color: rgb(178.5, 234.9, 220.5);
}
.form-range::-moz-range-track {
width: 100%;
@ -2618,7 +2618,7 @@ textarea.form-control-lg {
}
.form-floating > :disabled ~ label::after,
.form-floating > .form-control:disabled ~ label::after {
background-color: #2b2b2b;
background-color: rgb(42.5, 42.5, 42.5);
}
.input-group {
@ -2959,6 +2959,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2972,12 +2975,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -2989,12 +2992,12 @@ textarea.form-control-lg {
--bs-btn-bg: #adb5bd;
--bs-btn-border-color: #adb5bd;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #b9c0c7;
--bs-btn-hover-border-color: #b5bcc4;
--bs-btn-hover-bg: rgb(185.3, 192.1, 198.9);
--bs-btn-hover-border-color: rgb(181.2, 188.4, 195.6);
--bs-btn-focus-shadow-rgb: 147, 154, 161;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #bdc4ca;
--bs-btn-active-border-color: #b5bcc4;
--bs-btn-active-bg: rgb(189.4, 195.8, 202.2);
--bs-btn-active-border-color: rgb(181.2, 188.4, 195.6);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #adb5bd;
@ -3006,12 +3009,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00bc8c;
--bs-btn-border-color: #00bc8c;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #26c69d;
--bs-btn-hover-border-color: #1ac398;
--bs-btn-hover-bg: rgb(38.25, 198.05, 157.25);
--bs-btn-hover-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-focus-shadow-rgb: 0, 160, 119;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #33c9a3;
--bs-btn-active-border-color: #1ac398;
--bs-btn-active-bg: rgb(51, 201.4, 163);
--bs-btn-active-border-color: rgb(25.5, 194.7, 151.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #00bc8c;
@ -3023,12 +3026,12 @@ textarea.form-control-lg {
--bs-btn-bg: #3498db;
--bs-btn-border-color: #3498db;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #2c81ba;
--bs-btn-hover-border-color: #2a7aaf;
--bs-btn-hover-bg: rgb(44.2, 129.2, 186.15);
--bs-btn-hover-border-color: rgb(41.6, 121.6, 175.2);
--bs-btn-focus-shadow-rgb: 82, 167, 224;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #2a7aaf;
--bs-btn-active-border-color: #2772a4;
--bs-btn-active-bg: rgb(41.6, 121.6, 175.2);
--bs-btn-active-border-color: rgb(39, 114, 164.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #3498db;
@ -3040,12 +3043,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f39c12;
--bs-btn-border-color: #f39c12;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #f5ab36;
--bs-btn-hover-border-color: #f4a62a;
--bs-btn-hover-bg: rgb(244.8, 170.85, 53.55);
--bs-btn-hover-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-focus-shadow-rgb: 207, 133, 15;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #f5b041;
--bs-btn-active-border-color: #f4a62a;
--bs-btn-active-bg: rgb(245.4, 175.8, 65.4);
--bs-btn-active-border-color: rgb(244.2, 165.9, 41.7);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #f39c12;
@ -3057,12 +3060,12 @@ textarea.form-control-lg {
--bs-btn-bg: #e74c3c;
--bs-btn-border-color: #e74c3c;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #c44133;
--bs-btn-hover-border-color: #b93d30;
--bs-btn-hover-bg: rgb(196.35, 64.6, 51);
--bs-btn-hover-border-color: rgb(184.8, 60.8, 48);
--bs-btn-focus-shadow-rgb: 235, 103, 89;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #b93d30;
--bs-btn-active-border-color: #ad392d;
--bs-btn-active-bg: rgb(184.8, 60.8, 48);
--bs-btn-active-border-color: rgb(173.25, 57, 45);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #e74c3c;
@ -3074,11 +3077,11 @@ textarea.form-control-lg {
--bs-btn-bg: #303030;
--bs-btn-border-color: #303030;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #292929;
--bs-btn-hover-border-color: #262626;
--bs-btn-hover-bg: rgb(40.8, 40.8, 40.8);
--bs-btn-hover-border-color: rgb(38.4, 38.4, 38.4);
--bs-btn-focus-shadow-rgb: 79, 79, 79;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #262626;
--bs-btn-active-bg: rgb(38.4, 38.4, 38.4);
--bs-btn-active-border-color: #242424;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
@ -3091,12 +3094,12 @@ textarea.form-control-lg {
--bs-btn-bg: #dee2e6;
--bs-btn-border-color: #dee2e6;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #e3e6ea;
--bs-btn-hover-border-color: #e1e5e9;
--bs-btn-hover-bg: rgb(226.95, 230.35, 233.75);
--bs-btn-hover-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-focus-shadow-rgb: 189, 192, 196;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #e5e8eb;
--bs-btn-active-border-color: #e1e5e9;
--bs-btn-active-bg: rgb(228.6, 231.8, 235);
--bs-btn-active-border-color: rgb(225.3, 228.9, 232.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #dee2e6;
@ -4487,12 +4490,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23dee2e6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23004b38'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #80dec6;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%280, 75.2, 56%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4550,7 +4552,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4568,7 +4569,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4579,11 +4580,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4592,27 +4593,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2366d7ba'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2366d7ba'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28102, 214.8, 186%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28102, 214.8, 186%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -4657,16 +4658,16 @@ textarea.form-control-lg {
--bs-pagination-border-color: transparent;
--bs-pagination-border-radius: var(--bs-border-radius);
--bs-pagination-hover-color: #fff;
--bs-pagination-hover-bg: #00efb2;
--bs-pagination-hover-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-hover-border-color: transparent;
--bs-pagination-focus-color: var(--bs-link-hover-color);
--bs-pagination-focus-bg: var(--bs-secondary-bg);
--bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(0, 188, 140, 0.25);
--bs-pagination-active-color: #fff;
--bs-pagination-active-bg: #00efb2;
--bs-pagination-active-bg: rgb(0, 239, 177.9787234043);
--bs-pagination-active-border-color: transparent;
--bs-pagination-disabled-color: #fff;
--bs-pagination-disabled-bg: #007053;
--bs-pagination-disabled-bg: rgb(0, 111.5, 83.0319148936);
--bs-pagination-disabled-border-color: transparent;
display: flex;
padding-left: 0;
@ -5486,7 +5487,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6051,20 +6051,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6684,14 +6676,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
@import "variables.i386";
@import "variables.i386-dark";
@import "../../../../node_modules/bootstrap/scss/bootstrap";
.btn-outline-secondary {

File diff suppressed because it is too large Load diff

View file

@ -40,8 +40,8 @@ hr.my-3 {
}
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -74,7 +74,7 @@ hr.my-3 {
--bs-success: #6610f2;
--bs-info: #007bff;
--bs-warning: #ffc107;
--bs-danger: #873208;
--bs-danger: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 241, 100, 30;
@ -85,28 +85,28 @@ hr.my-3 {
--bs-danger-rgb: 135, 50, 8;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #60280c;
--bs-secondary-text-emphasis: #00431c;
--bs-success-text-emphasis: #290661;
--bs-info-text-emphasis: #003166;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #361403;
--bs-primary-text-emphasis: rgb(96.4, 40, 12);
--bs-secondary-text-emphasis: rgb(0, 67.2, 28);
--bs-success-text-emphasis: rgb(40.8, 6.4, 96.8);
--bs-info-text-emphasis: rgb(0, 49.2, 102);
--bs-warning-text-emphasis: rgb(102, 77.2, 2.8);
--bs-danger-text-emphasis: rgb(54.0376569038, 20.1740585774, 3.3623430962);
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #fce0d2;
--bs-secondary-bg-subtle: #cceeda;
--bs-success-bg-subtle: #e0cffc;
--bs-info-bg-subtle: #cce5ff;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #e7d6ce;
--bs-light-bg-subtle: #fcfcfd;
--bs-primary-bg-subtle: rgb(252.2, 224, 210);
--bs-secondary-bg-subtle: rgb(204, 237.6, 218);
--bs-success-bg-subtle: rgb(224.4, 207.2, 252.4);
--bs-info-bg-subtle: rgb(204, 228.6, 255);
--bs-warning-bg-subtle: rgb(255, 242.6, 205.4);
--bs-danger-bg-subtle: rgb(231.0188284519, 214.0870292887, 205.6811715481);
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #f9c1a5;
--bs-secondary-border-subtle: #99dcb5;
--bs-success-border-subtle: #c29ffa;
--bs-info-border-subtle: #99caff;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #cfad9c;
--bs-primary-border-subtle: rgb(249.4, 193, 165);
--bs-secondary-border-subtle: rgb(153, 220.2, 181);
--bs-success-border-subtle: rgb(193.8, 159.4, 249.8);
--bs-info-border-subtle: rgb(153, 202.2, 255);
--bs-warning-border-subtle: rgb(255, 230.2, 155.8);
--bs-danger-border-subtle: rgb(207.0376569038, 173.1740585774, 156.3623430962);
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
@ -137,7 +137,7 @@ hr.my-3 {
--bs-link-color: #f1641e;
--bs-link-color-rgb: 241, 100, 30;
--bs-link-decoration: none;
--bs-link-hover-color: #c15018;
--bs-link-hover-color: rgb(192.8, 80, 24);
--bs-link-hover-color-rgb: 193, 80, 24;
--bs-code-color: #d63384;
--bs-highlight-color: #495057;
@ -162,8 +162,8 @@ hr.my-3 {
--bs-focus-ring-color: rgba(241, 100, 30, 0.25);
--bs-form-valid-color: #007bff;
--bs-form-valid-border-color: #007bff;
--bs-form-invalid-color: #873208;
--bs-form-invalid-border-color: #873208;
--bs-form-invalid-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-form-invalid-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
}
[data-bs-theme=dark] {
@ -180,46 +180,46 @@ hr.my-3 {
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg: rgb(42.5, 47.5, 52.5);
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #f7a278;
--bs-secondary-text-emphasis: #66cb90;
--bs-success-text-emphasis: #a370f7;
--bs-info-text-emphasis: #66b0ff;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #b7846b;
--bs-primary-text-emphasis: rgb(246.6, 162, 120);
--bs-secondary-text-emphasis: rgb(102, 202.8, 144);
--bs-success-text-emphasis: rgb(163.2, 111.6, 247.2);
--bs-info-text-emphasis: rgb(102, 175.8, 255);
--bs-warning-text-emphasis: rgb(255, 217.8, 106.2);
--bs-danger-text-emphasis: rgb(183.0564853556, 132.2610878661, 107.0435146444);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #301406;
--bs-secondary-bg-subtle: #00220e;
--bs-success-bg-subtle: #140330;
--bs-info-bg-subtle: #001933;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #1b0a02;
--bs-primary-bg-subtle: rgb(48.2, 20, 6);
--bs-secondary-bg-subtle: rgb(0, 33.6, 14);
--bs-success-bg-subtle: rgb(20.4, 3.2, 48.4);
--bs-info-bg-subtle: rgb(0, 24.6, 51);
--bs-warning-bg-subtle: rgb(51, 38.6, 1.4);
--bs-danger-bg-subtle: rgb(27.0188284519, 10.0870292887, 1.6811715481);
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #2b2e31;
--bs-primary-border-subtle: #913c12;
--bs-secondary-border-subtle: #00652a;
--bs-success-border-subtle: #3d0a91;
--bs-info-border-subtle: #004a99;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #511e05;
--bs-primary-border-subtle: rgb(144.6, 60, 18);
--bs-secondary-border-subtle: rgb(0, 100.8, 42);
--bs-success-border-subtle: rgb(61.2, 9.6, 145.2);
--bs-info-border-subtle: rgb(0, 73.8, 153);
--bs-warning-border-subtle: rgb(153, 115.8, 4.2);
--bs-danger-border-subtle: rgb(81.0564853556, 30.2610878661, 5.0435146444);
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #f7a278;
--bs-link-hover-color: #f9b593;
--bs-link-color: rgb(246.6, 162, 120);
--bs-link-hover-color: rgb(248.28, 180.6, 147);
--bs-link-color-rgb: 247, 162, 120;
--bs-link-hover-color-rgb: 249, 181, 147;
--bs-code-color: #e685b5;
--bs-link-hover-color-rgb: 248, 181, 147;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-highlight-bg: rgb(102, 77.2, 2.8);
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #66cb90;
--bs-form-valid-border-color: #66cb90;
--bs-form-invalid-color: #e891a6;
--bs-form-invalid-border-color: #e891a6;
--bs-form-valid-color: rgb(102, 202.8, 144);
--bs-form-valid-border-color: rgb(102, 202.8, 144);
--bs-form-invalid-color: rgb(231.6, 145.2, 165.6);
--bs-form-invalid-border-color: rgb(231.6, 145.2, 165.6);
}
*,
@ -1950,13 +1950,13 @@ progress {
.table-primary {
--bs-table-color: #222;
--bs-table-bg: #fce0d2;
--bs-table-border-color: #d0baaf;
--bs-table-striped-bg: #f1d7c9;
--bs-table-bg: rgb(252.2, 224, 210);
--bs-table-border-color: rgb(208.56, 186, 174.8);
--bs-table-striped-bg: rgb(241.29, 214.5, 201.2);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e6cdc0;
--bs-table-active-bg: rgb(230.38, 205, 192.4);
--bs-table-active-color: #222;
--bs-table-hover-bg: #ecd2c5;
--bs-table-hover-bg: rgb(235.835, 209.75, 196.8);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1964,13 +1964,13 @@ progress {
.table-secondary {
--bs-table-color: #222;
--bs-table-bg: #cceeda;
--bs-table-border-color: #aac5b5;
--bs-table-striped-bg: #c4e4d1;
--bs-table-bg: rgb(204, 237.6, 218);
--bs-table-border-color: rgb(170, 196.88, 181.2);
--bs-table-striped-bg: rgb(195.5, 227.42, 208.8);
--bs-table-striped-color: #222;
--bs-table-active-bg: #bbdac8;
--bs-table-active-bg: rgb(187, 217.24, 199.6);
--bs-table-active-color: #222;
--bs-table-hover-bg: #bfdfcc;
--bs-table-hover-bg: rgb(191.25, 222.33, 204.2);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1978,13 +1978,13 @@ progress {
.table-success {
--bs-table-color: #222;
--bs-table-bg: #e0cffc;
--bs-table-border-color: #baacd0;
--bs-table-striped-bg: #d7c6f1;
--bs-table-bg: rgb(224.4, 207.2, 252.4);
--bs-table-border-color: rgb(186.32, 172.56, 208.72);
--bs-table-striped-bg: rgb(214.88, 198.54, 241.48);
--bs-table-striped-color: #222;
--bs-table-active-bg: #cdbee6;
--bs-table-active-bg: rgb(205.36, 189.88, 230.56);
--bs-table-active-color: #222;
--bs-table-hover-bg: #d2c2ec;
--bs-table-hover-bg: rgb(210.12, 194.21, 236.02);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1992,13 +1992,13 @@ progress {
.table-info {
--bs-table-color: #222;
--bs-table-bg: #cce5ff;
--bs-table-border-color: #aabed3;
--bs-table-striped-bg: #c4dbf4;
--bs-table-bg: rgb(204, 228.6, 255);
--bs-table-border-color: rgb(170, 189.68, 210.8);
--bs-table-striped-bg: rgb(195.5, 218.87, 243.95);
--bs-table-striped-color: #222;
--bs-table-active-bg: #bbd2e9;
--bs-table-active-bg: rgb(187, 209.14, 232.9);
--bs-table-active-color: #222;
--bs-table-hover-bg: #bfd6ee;
--bs-table-hover-bg: rgb(191.25, 214.005, 238.425);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2006,13 +2006,13 @@ progress {
.table-warning {
--bs-table-color: #222;
--bs-table-bg: #fff3cd;
--bs-table-border-color: #d3c9ab;
--bs-table-striped-bg: #f4e9c4;
--bs-table-bg: rgb(255, 242.6, 205.4);
--bs-table-border-color: rgb(210.8, 200.88, 171.12);
--bs-table-striped-bg: rgb(243.95, 232.17, 196.83);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e9debc;
--bs-table-active-bg: rgb(232.9, 221.74, 188.26);
--bs-table-active-color: #222;
--bs-table-hover-bg: #eee3c0;
--bs-table-hover-bg: rgb(238.425, 226.955, 192.545);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2020,13 +2020,13 @@ progress {
.table-danger {
--bs-table-color: #222;
--bs-table-bg: #e7d6ce;
--bs-table-border-color: #c0b2ac;
--bs-table-striped-bg: #ddcdc5;
--bs-table-bg: rgb(231.0188284519, 214.0870292887, 205.6811715481);
--bs-table-border-color: rgb(191.6150627615, 178.069623431, 171.3449372385);
--bs-table-striped-bg: rgb(221.1678870293, 205.0826778243, 197.0971129707);
--bs-table-striped-color: #222;
--bs-table-active-bg: #d3c4bd;
--bs-table-active-bg: rgb(211.3169456067, 196.0783263598, 188.5130543933);
--bs-table-active-color: #222;
--bs-table-hover-bg: #d8c9c1;
--bs-table-hover-bg: rgb(216.242416318, 200.5805020921, 192.805083682);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2035,12 +2035,12 @@ progress {
.table-light {
--bs-table-color: #222;
--bs-table-bg: #f8f9fa;
--bs-table-border-color: #cdcecf;
--bs-table-striped-bg: #edeeef;
--bs-table-border-color: rgb(205.2, 206, 206.8);
--bs-table-striped-bg: rgb(237.3, 238.25, 239.2);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e3e4e4;
--bs-table-active-bg: rgb(226.6, 227.5, 228.4);
--bs-table-active-color: #222;
--bs-table-hover-bg: #e8e9ea;
--bs-table-hover-bg: rgb(231.95, 232.875, 233.8);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2049,12 +2049,12 @@ progress {
.table-dark {
--bs-table-color: #fff;
--bs-table-bg: #212529;
--bs-table-border-color: #4d5154;
--bs-table-striped-bg: #2c3034;
--bs-table-border-color: rgb(77.4, 80.6, 83.8);
--bs-table-striped-bg: rgb(44.1, 47.9, 51.7);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #373b3e;
--bs-table-active-bg: rgb(55.2, 58.8, 62.4);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #323539;
--bs-table-hover-bg: rgb(49.65, 53.35, 57.05);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2154,7 +2154,7 @@ progress {
.form-control:focus {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
}
@ -2299,7 +2299,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.75);
}
@ -2383,7 +2383,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
}
@ -2430,7 +2430,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23f8b28f'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28248, 177.5, 142.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2500,7 +2500,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #fbd1bc;
background-color: rgb(250.8, 208.5, 187.5);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2526,7 +2526,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #fbd1bc;
background-color: rgb(250.8, 208.5, 187.5);
}
.form-range::-moz-range-track {
width: 100%;
@ -2849,7 +2849,7 @@ textarea.form-control-lg {
.was-validated .form-control:invalid, .form-control.is-invalid {
border-color: var(--bs-form-invalid-border-color);
padding-right: calc(1.5em + 0.75rem);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23873208'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23873208' stroke='none'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29' stroke='none'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right calc(0.375em + 0.1875rem) center;
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@ -2868,7 +2868,7 @@ textarea.form-control-lg {
border-color: var(--bs-form-invalid-border-color);
}
.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] {
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23873208'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23873208' stroke='none'/%3e%3c/svg%3e");
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29' stroke='none'/%3e%3c/svg%3e");
padding-right: 4.125rem;
background-position: right 0.75rem center, center right 2.25rem;
background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@ -2974,6 +2974,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2987,12 +2990,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f1641e;
--bs-btn-border-color: #f1641e;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #cd551a;
--bs-btn-hover-border-color: #c15018;
--bs-btn-hover-bg: rgb(204.85, 85, 25.5);
--bs-btn-hover-border-color: rgb(192.8, 80, 24);
--bs-btn-focus-shadow-rgb: 243, 123, 64;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #c15018;
--bs-btn-active-border-color: #b54b17;
--bs-btn-active-bg: rgb(192.8, 80, 24);
--bs-btn-active-border-color: rgb(180.75, 75, 22.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #f1641e;
@ -3004,12 +3007,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00a846;
--bs-btn-border-color: #00a846;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #008f3c;
--bs-btn-hover-border-color: #008638;
--bs-btn-hover-bg: rgb(0, 142.8, 59.5);
--bs-btn-hover-border-color: rgb(0, 134.4, 56);
--bs-btn-focus-shadow-rgb: 38, 181, 98;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #008638;
--bs-btn-active-border-color: #007e35;
--bs-btn-active-bg: rgb(0, 134.4, 56);
--bs-btn-active-border-color: rgb(0, 126, 52.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #00a846;
@ -3021,12 +3024,12 @@ textarea.form-control-lg {
--bs-btn-bg: #6610f2;
--bs-btn-border-color: #6610f2;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #570ece;
--bs-btn-hover-border-color: #520dc2;
--bs-btn-hover-bg: rgb(86.7, 13.6, 205.7);
--bs-btn-hover-border-color: rgb(81.6, 12.8, 193.6);
--bs-btn-focus-shadow-rgb: 125, 52, 244;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #520dc2;
--bs-btn-active-border-color: #4d0cb6;
--bs-btn-active-bg: rgb(81.6, 12.8, 193.6);
--bs-btn-active-border-color: rgb(76.5, 12, 181.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #6610f2;
@ -3038,12 +3041,12 @@ textarea.form-control-lg {
--bs-btn-bg: #007bff;
--bs-btn-border-color: #007bff;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0069d9;
--bs-btn-hover-border-color: #0062cc;
--bs-btn-hover-bg: rgb(0, 104.55, 216.75);
--bs-btn-hover-border-color: rgb(0, 98.4, 204);
--bs-btn-focus-shadow-rgb: 38, 143, 255;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0062cc;
--bs-btn-active-border-color: #005cbf;
--bs-btn-active-bg: rgb(0, 98.4, 204);
--bs-btn-active-border-color: rgb(0, 92.25, 191.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #007bff;
@ -3055,12 +3058,12 @@ textarea.form-control-lg {
--bs-btn-bg: #ffc107;
--bs-btn-border-color: #ffc107;
--bs-btn-hover-color: #222;
--bs-btn-hover-bg: #ffca2c;
--bs-btn-hover-border-color: #ffc720;
--bs-btn-hover-bg: rgb(255, 202.3, 44.2);
--bs-btn-hover-border-color: rgb(255, 199.2, 31.8);
--bs-btn-focus-shadow-rgb: 222, 169, 11;
--bs-btn-active-color: #222;
--bs-btn-active-bg: #ffcd39;
--bs-btn-active-border-color: #ffc720;
--bs-btn-active-bg: rgb(255, 205.4, 56.6);
--bs-btn-active-border-color: rgb(255, 199.2, 31.8);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #222;
--bs-btn-disabled-bg: #ffc107;
@ -3069,19 +3072,19 @@ textarea.form-control-lg {
.btn-danger {
--bs-btn-color: #fff;
--bs-btn-bg: #873208;
--bs-btn-border-color: #873208;
--bs-btn-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #732b07;
--bs-btn-hover-border-color: #6c2806;
--bs-btn-hover-bg: rgb(114.8300209205, 42.869874477, 7.1449790795);
--bs-btn-hover-border-color: rgb(108.0753138075, 40.3481171548, 6.7246861925);
--bs-btn-focus-shadow-rgb: 153, 81, 45;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #6c2806;
--bs-btn-active-border-color: #652606;
--bs-btn-active-bg: rgb(108.0753138075, 40.3481171548, 6.7246861925);
--bs-btn-active-border-color: rgb(101.3206066946, 37.8263598326, 6.3043933054);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #873208;
--bs-btn-disabled-border-color: #873208;
--bs-btn-disabled-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-disabled-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
}
.btn-light {
@ -3089,12 +3092,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #f8f9fa;
--bs-btn-hover-color: #222;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #c6c7c8;
--bs-btn-hover-bg: rgb(210.8, 211.65, 212.5);
--bs-btn-hover-border-color: rgb(198.4, 199.2, 200);
--bs-btn-focus-shadow-rgb: 216, 217, 218;
--bs-btn-active-color: #222;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #babbbc;
--bs-btn-active-bg: rgb(198.4, 199.2, 200);
--bs-btn-active-border-color: rgb(186, 186.75, 187.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #222;
--bs-btn-disabled-bg: #f8f9fa;
@ -3106,12 +3109,12 @@ textarea.form-control-lg {
--bs-btn-bg: #212529;
--bs-btn-border-color: #212529;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #424649;
--bs-btn-hover-border-color: #373b3e;
--bs-btn-hover-bg: rgb(66.3, 69.7, 73.1);
--bs-btn-hover-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-focus-shadow-rgb: 66, 70, 73;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #4d5154;
--bs-btn-active-border-color: #373b3e;
--bs-btn-active-bg: rgb(77.4, 80.6, 83.8);
--bs-btn-active-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #212529;
@ -3204,19 +3207,19 @@ textarea.form-control-lg {
}
.btn-outline-danger {
--bs-btn-color: #873208;
--bs-btn-border-color: #873208;
--bs-btn-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #873208;
--bs-btn-hover-border-color: #873208;
--bs-btn-hover-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-hover-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-focus-shadow-rgb: 135, 50, 8;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #873208;
--bs-btn-active-border-color: #873208;
--bs-btn-active-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-active-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #873208;
--bs-btn-disabled-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: #873208;
--bs-btn-disabled-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-gradient: none;
}
@ -4498,12 +4501,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23495057' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2360280c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #f8b28f;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%2896.4, 40, 12%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4561,7 +4563,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4579,7 +4580,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4590,11 +4591,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4603,27 +4604,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f7a278'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f7a278'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28246.6, 162, 120%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28246.6, 162, 120%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -5497,7 +5498,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6062,20 +6062,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6695,14 +6687,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {
@ -6857,8 +6846,8 @@ textarea.form-control-lg {
text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important;
}
.link-danger:hover, .link-danger:focus {
color: RGBA(108, 40, 6, var(--bs-link-opacity, 1)) !important;
text-decoration-color: RGBA(108, 40, 6, var(--bs-link-underline-opacity, 1)) !important;
color: RGBA(108, 40, 7, var(--bs-link-opacity, 1)) !important;
text-decoration-color: RGBA(108, 40, 7, var(--bs-link-underline-opacity, 1)) !important;
}
.link-light {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -34,7 +34,7 @@
--bs-success: #6610f2;
--bs-info: #007bff;
--bs-warning: #ffc107;
--bs-danger: #8c3409;
--bs-danger: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 241, 100, 30;
@ -45,28 +45,28 @@
--bs-danger-rgb: 140, 52, 9;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #60280c;
--bs-primary-text-emphasis: rgb(96.4, 40, 12);
--bs-secondary-text-emphasis: #500000;
--bs-success-text-emphasis: #290661;
--bs-info-text-emphasis: #003166;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #381504;
--bs-success-text-emphasis: rgb(40.8, 6.4, 96.8);
--bs-info-text-emphasis: rgb(0, 49.2, 102);
--bs-warning-text-emphasis: rgb(102, 77.2, 2.8);
--bs-danger-text-emphasis: rgb(55.9581589958, 20.8910460251, 3.4818410042);
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #fce0d2;
--bs-primary-bg-subtle: rgb(252.2, 224, 210);
--bs-secondary-bg-subtle: #f4cccc;
--bs-success-bg-subtle: #e0cffc;
--bs-info-bg-subtle: #cce5ff;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #e8d6ce;
--bs-light-bg-subtle: #fcfcfd;
--bs-success-bg-subtle: rgb(224.4, 207.2, 252.4);
--bs-info-bg-subtle: rgb(204, 228.6, 255);
--bs-warning-bg-subtle: rgb(255, 242.6, 205.4);
--bs-danger-bg-subtle: rgb(231.9790794979, 214.4455230126, 205.7409205021);
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #f9c1a5;
--bs-primary-border-subtle: rgb(249.4, 193, 165);
--bs-secondary-border-subtle: #e99999;
--bs-success-border-subtle: #c29ffa;
--bs-info-border-subtle: #99caff;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #d1ae9d;
--bs-success-border-subtle: rgb(193.8, 159.4, 249.8);
--bs-info-border-subtle: rgb(153, 202.2, 255);
--bs-warning-border-subtle: rgb(255, 230.2, 155.8);
--bs-danger-border-subtle: rgb(208.9581589958, 173.8910460251, 156.4818410042);
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
@ -97,7 +97,7 @@
--bs-link-color: #f1641e;
--bs-link-color-rgb: 241, 100, 30;
--bs-link-decoration: none;
--bs-link-hover-color: #c15018;
--bs-link-hover-color: rgb(192.8, 80, 24);
--bs-link-hover-color-rgb: 193, 80, 24;
--bs-code-color: #d63384;
--bs-highlight-color: #495057;
@ -122,8 +122,8 @@
--bs-focus-ring-color: rgba(241, 100, 30, 0.25);
--bs-form-valid-color: #007bff;
--bs-form-valid-border-color: #007bff;
--bs-form-invalid-color: #8c3409;
--bs-form-invalid-border-color: #8c3409;
--bs-form-invalid-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-form-invalid-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
}
[data-bs-theme=dark] {
@ -140,46 +140,46 @@
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg: rgb(42.5, 47.5, 52.5);
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #f7a278;
--bs-primary-text-emphasis: rgb(246.6, 162, 120);
--bs-secondary-text-emphasis: #de6666;
--bs-success-text-emphasis: #a370f7;
--bs-info-text-emphasis: #66b0ff;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #ba856b;
--bs-success-text-emphasis: rgb(163.2, 111.6, 247.2);
--bs-info-text-emphasis: rgb(102, 175.8, 255);
--bs-warning-text-emphasis: rgb(255, 217.8, 106.2);
--bs-danger-text-emphasis: rgb(185.9372384937, 133.3365690377, 107.2227615063);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #301406;
--bs-primary-bg-subtle: rgb(48.2, 20, 6);
--bs-secondary-bg-subtle: #280000;
--bs-success-bg-subtle: #140330;
--bs-info-bg-subtle: #001933;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #1c0a02;
--bs-success-bg-subtle: rgb(20.4, 3.2, 48.4);
--bs-info-bg-subtle: rgb(0, 24.6, 51);
--bs-warning-bg-subtle: rgb(51, 38.6, 1.4);
--bs-danger-bg-subtle: rgb(27.9790794979, 10.4455230126, 1.7409205021);
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #2b2e31;
--bs-primary-border-subtle: #913c12;
--bs-primary-border-subtle: rgb(144.6, 60, 18);
--bs-secondary-border-subtle: #780000;
--bs-success-border-subtle: #3d0a91;
--bs-info-border-subtle: #004a99;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #541f05;
--bs-success-border-subtle: rgb(61.2, 9.6, 145.2);
--bs-info-border-subtle: rgb(0, 73.8, 153);
--bs-warning-border-subtle: rgb(153, 115.8, 4.2);
--bs-danger-border-subtle: rgb(83.9372384937, 31.3365690377, 5.2227615063);
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #f7a278;
--bs-link-hover-color: #f9b593;
--bs-link-color: rgb(246.6, 162, 120);
--bs-link-hover-color: rgb(248.28, 180.6, 147);
--bs-link-color-rgb: 247, 162, 120;
--bs-link-hover-color-rgb: 249, 181, 147;
--bs-code-color: #e685b5;
--bs-link-hover-color-rgb: 248, 181, 147;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-highlight-bg: rgb(102, 77.2, 2.8);
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #66cb90;
--bs-form-valid-border-color: #66cb90;
--bs-form-invalid-color: #e891a6;
--bs-form-invalid-border-color: #e891a6;
--bs-form-valid-color: rgb(102, 202.8, 144);
--bs-form-valid-border-color: rgb(102, 202.8, 144);
--bs-form-invalid-color: rgb(231.6, 145.2, 165.6);
--bs-form-invalid-border-color: rgb(231.6, 145.2, 165.6);
}
*,
@ -1934,13 +1934,13 @@ progress {
.table-primary {
--bs-table-color: #222;
--bs-table-bg: #fce0d2;
--bs-table-border-color: #d0baaf;
--bs-table-striped-bg: #f1d7c9;
--bs-table-bg: rgb(252.2, 224, 210);
--bs-table-border-color: rgb(208.56, 186, 174.8);
--bs-table-striped-bg: rgb(241.29, 214.5, 201.2);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e6cdc0;
--bs-table-active-bg: rgb(230.38, 205, 192.4);
--bs-table-active-color: #222;
--bs-table-hover-bg: #ecd2c5;
--bs-table-hover-bg: rgb(235.835, 209.75, 196.8);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1950,11 +1950,11 @@ progress {
--bs-table-color: #222;
--bs-table-bg: #f4cccc;
--bs-table-border-color: #caaaaa;
--bs-table-striped-bg: #eac4c4;
--bs-table-striped-bg: rgb(233.5, 195.5, 195.5);
--bs-table-striped-color: #222;
--bs-table-active-bg: #dfbbbb;
--bs-table-active-color: #222;
--bs-table-hover-bg: #e4bfbf;
--bs-table-hover-bg: rgb(228.25, 191.25, 191.25);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1962,13 +1962,13 @@ progress {
.table-success {
--bs-table-color: #222;
--bs-table-bg: #e0cffc;
--bs-table-border-color: #baacd0;
--bs-table-striped-bg: #d7c6f1;
--bs-table-bg: rgb(224.4, 207.2, 252.4);
--bs-table-border-color: rgb(186.32, 172.56, 208.72);
--bs-table-striped-bg: rgb(214.88, 198.54, 241.48);
--bs-table-striped-color: #222;
--bs-table-active-bg: #cdbee6;
--bs-table-active-bg: rgb(205.36, 189.88, 230.56);
--bs-table-active-color: #222;
--bs-table-hover-bg: #d2c2ec;
--bs-table-hover-bg: rgb(210.12, 194.21, 236.02);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1976,13 +1976,13 @@ progress {
.table-info {
--bs-table-color: #222;
--bs-table-bg: #cce5ff;
--bs-table-border-color: #aabed3;
--bs-table-striped-bg: #c4dbf4;
--bs-table-bg: rgb(204, 228.6, 255);
--bs-table-border-color: rgb(170, 189.68, 210.8);
--bs-table-striped-bg: rgb(195.5, 218.87, 243.95);
--bs-table-striped-color: #222;
--bs-table-active-bg: #bbd2e9;
--bs-table-active-bg: rgb(187, 209.14, 232.9);
--bs-table-active-color: #222;
--bs-table-hover-bg: #bfd6ee;
--bs-table-hover-bg: rgb(191.25, 214.005, 238.425);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1990,13 +1990,13 @@ progress {
.table-warning {
--bs-table-color: #222;
--bs-table-bg: #fff3cd;
--bs-table-border-color: #d3c9ab;
--bs-table-striped-bg: #f4e9c4;
--bs-table-bg: rgb(255, 242.6, 205.4);
--bs-table-border-color: rgb(210.8, 200.88, 171.12);
--bs-table-striped-bg: rgb(243.95, 232.17, 196.83);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e9debc;
--bs-table-active-bg: rgb(232.9, 221.74, 188.26);
--bs-table-active-color: #222;
--bs-table-hover-bg: #eee3c0;
--bs-table-hover-bg: rgb(238.425, 226.955, 192.545);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2004,13 +2004,13 @@ progress {
.table-danger {
--bs-table-color: #222;
--bs-table-bg: #e8d6ce;
--bs-table-border-color: #c0b2ac;
--bs-table-striped-bg: #decdc5;
--bs-table-bg: rgb(231.9790794979, 214.4455230126, 205.7409205021);
--bs-table-border-color: rgb(192.3832635983, 178.35641841, 171.3927364017);
--bs-table-striped-bg: rgb(222.080125523, 205.4232468619, 197.153874477);
--bs-table-striped-color: #222;
--bs-table-active-bg: #d4c4bd;
--bs-table-active-bg: rgb(212.1811715481, 196.4009707113, 188.5668284519);
--bs-table-active-color: #222;
--bs-table-hover-bg: #d9c9c1;
--bs-table-hover-bg: rgb(217.1306485356, 200.9121087866, 192.8603514644);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2019,12 +2019,12 @@ progress {
.table-light {
--bs-table-color: #222;
--bs-table-bg: #f8f9fa;
--bs-table-border-color: #cdcecf;
--bs-table-striped-bg: #edeeef;
--bs-table-border-color: rgb(205.2, 206, 206.8);
--bs-table-striped-bg: rgb(237.3, 238.25, 239.2);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e3e4e4;
--bs-table-active-bg: rgb(226.6, 227.5, 228.4);
--bs-table-active-color: #222;
--bs-table-hover-bg: #e8e9ea;
--bs-table-hover-bg: rgb(231.95, 232.875, 233.8);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2033,12 +2033,12 @@ progress {
.table-dark {
--bs-table-color: #fff;
--bs-table-bg: #212529;
--bs-table-border-color: #4d5154;
--bs-table-striped-bg: #2c3034;
--bs-table-border-color: rgb(77.4, 80.6, 83.8);
--bs-table-striped-bg: rgb(44.1, 47.9, 51.7);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #373b3e;
--bs-table-active-bg: rgb(55.2, 58.8, 62.4);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #323539;
--bs-table-hover-bg: rgb(49.65, 53.35, 57.05);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2138,7 +2138,7 @@ progress {
.form-control:focus {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
}
@ -2283,7 +2283,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.75);
}
@ -2367,7 +2367,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
}
@ -2414,7 +2414,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23f8b28f'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28248, 177.5, 142.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2484,7 +2484,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #fbd1bc;
background-color: rgb(250.8, 208.5, 187.5);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2510,7 +2510,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #fbd1bc;
background-color: rgb(250.8, 208.5, 187.5);
}
.form-range::-moz-range-track {
width: 100%;
@ -2833,7 +2833,7 @@ textarea.form-control-lg {
.was-validated .form-control:invalid, .form-control.is-invalid {
border-color: var(--bs-form-invalid-border-color);
padding-right: calc(1.5em + 0.75rem);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%238c3409'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%238c3409' stroke='none'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='rgb%28139.8953974895, 52.2276150628, 8.7046025105%29'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='rgb%28139.8953974895, 52.2276150628, 8.7046025105%29' stroke='none'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right calc(0.375em + 0.1875rem) center;
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@ -2852,7 +2852,7 @@ textarea.form-control-lg {
border-color: var(--bs-form-invalid-border-color);
}
.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] {
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%238c3409'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%238c3409' stroke='none'/%3e%3c/svg%3e");
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='rgb%28139.8953974895, 52.2276150628, 8.7046025105%29'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='rgb%28139.8953974895, 52.2276150628, 8.7046025105%29' stroke='none'/%3e%3c/svg%3e");
padding-right: 4.125rem;
background-position: right 0.75rem center, center right 2.25rem;
background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@ -2958,6 +2958,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2971,12 +2974,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f1641e;
--bs-btn-border-color: #f1641e;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #cd551a;
--bs-btn-hover-border-color: #c15018;
--bs-btn-hover-bg: rgb(204.85, 85, 25.5);
--bs-btn-hover-border-color: rgb(192.8, 80, 24);
--bs-btn-focus-shadow-rgb: 243, 123, 64;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #c15018;
--bs-btn-active-border-color: #b54b17;
--bs-btn-active-bg: rgb(192.8, 80, 24);
--bs-btn-active-border-color: rgb(180.75, 75, 22.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #f1641e;
@ -3005,12 +3008,12 @@ textarea.form-control-lg {
--bs-btn-bg: #6610f2;
--bs-btn-border-color: #6610f2;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #570ece;
--bs-btn-hover-border-color: #520dc2;
--bs-btn-hover-bg: rgb(86.7, 13.6, 205.7);
--bs-btn-hover-border-color: rgb(81.6, 12.8, 193.6);
--bs-btn-focus-shadow-rgb: 125, 52, 244;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #520dc2;
--bs-btn-active-border-color: #4d0cb6;
--bs-btn-active-bg: rgb(81.6, 12.8, 193.6);
--bs-btn-active-border-color: rgb(76.5, 12, 181.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #6610f2;
@ -3022,12 +3025,12 @@ textarea.form-control-lg {
--bs-btn-bg: #007bff;
--bs-btn-border-color: #007bff;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0069d9;
--bs-btn-hover-border-color: #0062cc;
--bs-btn-hover-bg: rgb(0, 104.55, 216.75);
--bs-btn-hover-border-color: rgb(0, 98.4, 204);
--bs-btn-focus-shadow-rgb: 38, 143, 255;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0062cc;
--bs-btn-active-border-color: #005cbf;
--bs-btn-active-bg: rgb(0, 98.4, 204);
--bs-btn-active-border-color: rgb(0, 92.25, 191.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #007bff;
@ -3039,12 +3042,12 @@ textarea.form-control-lg {
--bs-btn-bg: #ffc107;
--bs-btn-border-color: #ffc107;
--bs-btn-hover-color: #222;
--bs-btn-hover-bg: #ffca2c;
--bs-btn-hover-border-color: #ffc720;
--bs-btn-hover-bg: rgb(255, 202.3, 44.2);
--bs-btn-hover-border-color: rgb(255, 199.2, 31.8);
--bs-btn-focus-shadow-rgb: 222, 169, 11;
--bs-btn-active-color: #222;
--bs-btn-active-bg: #ffcd39;
--bs-btn-active-border-color: #ffc720;
--bs-btn-active-bg: rgb(255, 205.4, 56.6);
--bs-btn-active-border-color: rgb(255, 199.2, 31.8);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #222;
--bs-btn-disabled-bg: #ffc107;
@ -3053,19 +3056,19 @@ textarea.form-control-lg {
.btn-danger {
--bs-btn-color: #fff;
--bs-btn-bg: #8c3409;
--bs-btn-border-color: #8c3409;
--bs-btn-bg: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #772c08;
--bs-btn-hover-border-color: #702a07;
--bs-btn-focus-shadow-rgb: 157, 82, 46;
--bs-btn-hover-bg: rgb(118.9110878661, 44.3934728033, 7.3989121339);
--bs-btn-hover-border-color: rgb(111.9163179916, 41.7820920502, 6.9636820084);
--bs-btn-focus-shadow-rgb: 157, 83, 46;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #702a07;
--bs-btn-active-border-color: #692707;
--bs-btn-active-bg: rgb(111.9163179916, 41.7820920502, 6.9636820084);
--bs-btn-active-border-color: rgb(104.9215481172, 39.1707112971, 6.5284518828);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #8c3409;
--bs-btn-disabled-border-color: #8c3409;
--bs-btn-disabled-bg: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-disabled-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
}
.btn-light {
@ -3073,12 +3076,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #f8f9fa;
--bs-btn-hover-color: #222;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #c6c7c8;
--bs-btn-hover-bg: rgb(210.8, 211.65, 212.5);
--bs-btn-hover-border-color: rgb(198.4, 199.2, 200);
--bs-btn-focus-shadow-rgb: 216, 217, 218;
--bs-btn-active-color: #222;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #babbbc;
--bs-btn-active-bg: rgb(198.4, 199.2, 200);
--bs-btn-active-border-color: rgb(186, 186.75, 187.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #222;
--bs-btn-disabled-bg: #f8f9fa;
@ -3090,12 +3093,12 @@ textarea.form-control-lg {
--bs-btn-bg: #212529;
--bs-btn-border-color: #212529;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #424649;
--bs-btn-hover-border-color: #373b3e;
--bs-btn-hover-bg: rgb(66.3, 69.7, 73.1);
--bs-btn-hover-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-focus-shadow-rgb: 66, 70, 73;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #4d5154;
--bs-btn-active-border-color: #373b3e;
--bs-btn-active-bg: rgb(77.4, 80.6, 83.8);
--bs-btn-active-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #212529;
@ -3188,19 +3191,19 @@ textarea.form-control-lg {
}
.btn-outline-danger {
--bs-btn-color: #8c3409;
--bs-btn-border-color: #8c3409;
--bs-btn-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #8c3409;
--bs-btn-hover-border-color: #8c3409;
--bs-btn-hover-bg: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-hover-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-focus-shadow-rgb: 140, 52, 9;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #8c3409;
--bs-btn-active-border-color: #8c3409;
--bs-btn-active-bg: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-active-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #8c3409;
--bs-btn-disabled-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: #8c3409;
--bs-btn-disabled-border-color: rgb(139.8953974895, 52.2276150628, 8.7046025105);
--bs-gradient: none;
}
@ -4486,12 +4489,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23495057' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2360280c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #f8b28f;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%2896.4, 40, 12%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4549,7 +4551,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4567,7 +4568,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4578,11 +4579,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4591,27 +4592,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f7a278'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f7a278'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28246.6, 162, 120%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28246.6, 162, 120%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -5485,7 +5486,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6050,20 +6050,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6683,14 +6675,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -34,7 +34,7 @@
--bs-success: #6610f2;
--bs-info: #007bff;
--bs-warning: #ffc107;
--bs-danger: #873208;
--bs-danger: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-light: #f8f9fa;
--bs-dark: #212529;
--bs-primary-rgb: 241, 100, 30;
@ -45,28 +45,28 @@
--bs-danger-rgb: 135, 50, 8;
--bs-light-rgb: 248, 249, 250;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #60280c;
--bs-secondary-text-emphasis: #00431c;
--bs-success-text-emphasis: #290661;
--bs-info-text-emphasis: #003166;
--bs-warning-text-emphasis: #664d03;
--bs-danger-text-emphasis: #361403;
--bs-primary-text-emphasis: rgb(96.4, 40, 12);
--bs-secondary-text-emphasis: rgb(0, 67.2, 28);
--bs-success-text-emphasis: rgb(40.8, 6.4, 96.8);
--bs-info-text-emphasis: rgb(0, 49.2, 102);
--bs-warning-text-emphasis: rgb(102, 77.2, 2.8);
--bs-danger-text-emphasis: rgb(54.0376569038, 20.1740585774, 3.3623430962);
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #fce0d2;
--bs-secondary-bg-subtle: #cceeda;
--bs-success-bg-subtle: #e0cffc;
--bs-info-bg-subtle: #cce5ff;
--bs-warning-bg-subtle: #fff3cd;
--bs-danger-bg-subtle: #e7d6ce;
--bs-light-bg-subtle: #fcfcfd;
--bs-primary-bg-subtle: rgb(252.2, 224, 210);
--bs-secondary-bg-subtle: rgb(204, 237.6, 218);
--bs-success-bg-subtle: rgb(224.4, 207.2, 252.4);
--bs-info-bg-subtle: rgb(204, 228.6, 255);
--bs-warning-bg-subtle: rgb(255, 242.6, 205.4);
--bs-danger-bg-subtle: rgb(231.0188284519, 214.0870292887, 205.6811715481);
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #f9c1a5;
--bs-secondary-border-subtle: #99dcb5;
--bs-success-border-subtle: #c29ffa;
--bs-info-border-subtle: #99caff;
--bs-warning-border-subtle: #ffe69c;
--bs-danger-border-subtle: #cfad9c;
--bs-primary-border-subtle: rgb(249.4, 193, 165);
--bs-secondary-border-subtle: rgb(153, 220.2, 181);
--bs-success-border-subtle: rgb(193.8, 159.4, 249.8);
--bs-info-border-subtle: rgb(153, 202.2, 255);
--bs-warning-border-subtle: rgb(255, 230.2, 155.8);
--bs-danger-border-subtle: rgb(207.0376569038, 173.1740585774, 156.3623430962);
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
--bs-white-rgb: 255, 255, 255;
@ -97,7 +97,7 @@
--bs-link-color: #f1641e;
--bs-link-color-rgb: 241, 100, 30;
--bs-link-decoration: none;
--bs-link-hover-color: #c15018;
--bs-link-hover-color: rgb(192.8, 80, 24);
--bs-link-hover-color-rgb: 193, 80, 24;
--bs-code-color: #d63384;
--bs-highlight-color: #495057;
@ -122,8 +122,8 @@
--bs-focus-ring-color: rgba(241, 100, 30, 0.25);
--bs-form-valid-color: #007bff;
--bs-form-valid-border-color: #007bff;
--bs-form-invalid-color: #873208;
--bs-form-invalid-border-color: #873208;
--bs-form-invalid-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-form-invalid-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
}
[data-bs-theme=dark] {
@ -140,46 +140,46 @@
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg: rgb(42.5, 47.5, 52.5);
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #f7a278;
--bs-secondary-text-emphasis: #66cb90;
--bs-success-text-emphasis: #a370f7;
--bs-info-text-emphasis: #66b0ff;
--bs-warning-text-emphasis: #ffda6a;
--bs-danger-text-emphasis: #b7846b;
--bs-primary-text-emphasis: rgb(246.6, 162, 120);
--bs-secondary-text-emphasis: rgb(102, 202.8, 144);
--bs-success-text-emphasis: rgb(163.2, 111.6, 247.2);
--bs-info-text-emphasis: rgb(102, 175.8, 255);
--bs-warning-text-emphasis: rgb(255, 217.8, 106.2);
--bs-danger-text-emphasis: rgb(183.0564853556, 132.2610878661, 107.0435146444);
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #301406;
--bs-secondary-bg-subtle: #00220e;
--bs-success-bg-subtle: #140330;
--bs-info-bg-subtle: #001933;
--bs-warning-bg-subtle: #332701;
--bs-danger-bg-subtle: #1b0a02;
--bs-primary-bg-subtle: rgb(48.2, 20, 6);
--bs-secondary-bg-subtle: rgb(0, 33.6, 14);
--bs-success-bg-subtle: rgb(20.4, 3.2, 48.4);
--bs-info-bg-subtle: rgb(0, 24.6, 51);
--bs-warning-bg-subtle: rgb(51, 38.6, 1.4);
--bs-danger-bg-subtle: rgb(27.0188284519, 10.0870292887, 1.6811715481);
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #2b2e31;
--bs-primary-border-subtle: #913c12;
--bs-secondary-border-subtle: #00652a;
--bs-success-border-subtle: #3d0a91;
--bs-info-border-subtle: #004a99;
--bs-warning-border-subtle: #997404;
--bs-danger-border-subtle: #511e05;
--bs-primary-border-subtle: rgb(144.6, 60, 18);
--bs-secondary-border-subtle: rgb(0, 100.8, 42);
--bs-success-border-subtle: rgb(61.2, 9.6, 145.2);
--bs-info-border-subtle: rgb(0, 73.8, 153);
--bs-warning-border-subtle: rgb(153, 115.8, 4.2);
--bs-danger-border-subtle: rgb(81.0564853556, 30.2610878661, 5.0435146444);
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #f7a278;
--bs-link-hover-color: #f9b593;
--bs-link-color: rgb(246.6, 162, 120);
--bs-link-hover-color: rgb(248.28, 180.6, 147);
--bs-link-color-rgb: 247, 162, 120;
--bs-link-hover-color-rgb: 249, 181, 147;
--bs-code-color: #e685b5;
--bs-link-hover-color-rgb: 248, 181, 147;
--bs-code-color: rgb(230.4, 132.6, 181.2);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #664d03;
--bs-highlight-bg: rgb(102, 77.2, 2.8);
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #66cb90;
--bs-form-valid-border-color: #66cb90;
--bs-form-invalid-color: #e891a6;
--bs-form-invalid-border-color: #e891a6;
--bs-form-valid-color: rgb(102, 202.8, 144);
--bs-form-valid-border-color: rgb(102, 202.8, 144);
--bs-form-invalid-color: rgb(231.6, 145.2, 165.6);
--bs-form-invalid-border-color: rgb(231.6, 145.2, 165.6);
}
*,
@ -1934,13 +1934,13 @@ progress {
.table-primary {
--bs-table-color: #222;
--bs-table-bg: #fce0d2;
--bs-table-border-color: #d0baaf;
--bs-table-striped-bg: #f1d7c9;
--bs-table-bg: rgb(252.2, 224, 210);
--bs-table-border-color: rgb(208.56, 186, 174.8);
--bs-table-striped-bg: rgb(241.29, 214.5, 201.2);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e6cdc0;
--bs-table-active-bg: rgb(230.38, 205, 192.4);
--bs-table-active-color: #222;
--bs-table-hover-bg: #ecd2c5;
--bs-table-hover-bg: rgb(235.835, 209.75, 196.8);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1948,13 +1948,13 @@ progress {
.table-secondary {
--bs-table-color: #222;
--bs-table-bg: #cceeda;
--bs-table-border-color: #aac5b5;
--bs-table-striped-bg: #c4e4d1;
--bs-table-bg: rgb(204, 237.6, 218);
--bs-table-border-color: rgb(170, 196.88, 181.2);
--bs-table-striped-bg: rgb(195.5, 227.42, 208.8);
--bs-table-striped-color: #222;
--bs-table-active-bg: #bbdac8;
--bs-table-active-bg: rgb(187, 217.24, 199.6);
--bs-table-active-color: #222;
--bs-table-hover-bg: #bfdfcc;
--bs-table-hover-bg: rgb(191.25, 222.33, 204.2);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1962,13 +1962,13 @@ progress {
.table-success {
--bs-table-color: #222;
--bs-table-bg: #e0cffc;
--bs-table-border-color: #baacd0;
--bs-table-striped-bg: #d7c6f1;
--bs-table-bg: rgb(224.4, 207.2, 252.4);
--bs-table-border-color: rgb(186.32, 172.56, 208.72);
--bs-table-striped-bg: rgb(214.88, 198.54, 241.48);
--bs-table-striped-color: #222;
--bs-table-active-bg: #cdbee6;
--bs-table-active-bg: rgb(205.36, 189.88, 230.56);
--bs-table-active-color: #222;
--bs-table-hover-bg: #d2c2ec;
--bs-table-hover-bg: rgb(210.12, 194.21, 236.02);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1976,13 +1976,13 @@ progress {
.table-info {
--bs-table-color: #222;
--bs-table-bg: #cce5ff;
--bs-table-border-color: #aabed3;
--bs-table-striped-bg: #c4dbf4;
--bs-table-bg: rgb(204, 228.6, 255);
--bs-table-border-color: rgb(170, 189.68, 210.8);
--bs-table-striped-bg: rgb(195.5, 218.87, 243.95);
--bs-table-striped-color: #222;
--bs-table-active-bg: #bbd2e9;
--bs-table-active-bg: rgb(187, 209.14, 232.9);
--bs-table-active-color: #222;
--bs-table-hover-bg: #bfd6ee;
--bs-table-hover-bg: rgb(191.25, 214.005, 238.425);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1990,13 +1990,13 @@ progress {
.table-warning {
--bs-table-color: #222;
--bs-table-bg: #fff3cd;
--bs-table-border-color: #d3c9ab;
--bs-table-striped-bg: #f4e9c4;
--bs-table-bg: rgb(255, 242.6, 205.4);
--bs-table-border-color: rgb(210.8, 200.88, 171.12);
--bs-table-striped-bg: rgb(243.95, 232.17, 196.83);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e9debc;
--bs-table-active-bg: rgb(232.9, 221.74, 188.26);
--bs-table-active-color: #222;
--bs-table-hover-bg: #eee3c0;
--bs-table-hover-bg: rgb(238.425, 226.955, 192.545);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2004,13 +2004,13 @@ progress {
.table-danger {
--bs-table-color: #222;
--bs-table-bg: #e7d6ce;
--bs-table-border-color: #c0b2ac;
--bs-table-striped-bg: #ddcdc5;
--bs-table-bg: rgb(231.0188284519, 214.0870292887, 205.6811715481);
--bs-table-border-color: rgb(191.6150627615, 178.069623431, 171.3449372385);
--bs-table-striped-bg: rgb(221.1678870293, 205.0826778243, 197.0971129707);
--bs-table-striped-color: #222;
--bs-table-active-bg: #d3c4bd;
--bs-table-active-bg: rgb(211.3169456067, 196.0783263598, 188.5130543933);
--bs-table-active-color: #222;
--bs-table-hover-bg: #d8c9c1;
--bs-table-hover-bg: rgb(216.242416318, 200.5805020921, 192.805083682);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2019,12 +2019,12 @@ progress {
.table-light {
--bs-table-color: #222;
--bs-table-bg: #f8f9fa;
--bs-table-border-color: #cdcecf;
--bs-table-striped-bg: #edeeef;
--bs-table-border-color: rgb(205.2, 206, 206.8);
--bs-table-striped-bg: rgb(237.3, 238.25, 239.2);
--bs-table-striped-color: #222;
--bs-table-active-bg: #e3e4e4;
--bs-table-active-bg: rgb(226.6, 227.5, 228.4);
--bs-table-active-color: #222;
--bs-table-hover-bg: #e8e9ea;
--bs-table-hover-bg: rgb(231.95, 232.875, 233.8);
--bs-table-hover-color: #222;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2033,12 +2033,12 @@ progress {
.table-dark {
--bs-table-color: #fff;
--bs-table-bg: #212529;
--bs-table-border-color: #4d5154;
--bs-table-striped-bg: #2c3034;
--bs-table-border-color: rgb(77.4, 80.6, 83.8);
--bs-table-striped-bg: rgb(44.1, 47.9, 51.7);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #373b3e;
--bs-table-active-bg: rgb(55.2, 58.8, 62.4);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #323539;
--bs-table-hover-bg: rgb(49.65, 53.35, 57.05);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2138,7 +2138,7 @@ progress {
.form-control:focus {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
}
@ -2283,7 +2283,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.75);
}
@ -2367,7 +2367,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #f8b28f;
border-color: rgb(248, 177.5, 142.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
}
@ -2414,7 +2414,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23f8b28f'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28248, 177.5, 142.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2484,7 +2484,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #fbd1bc;
background-color: rgb(250.8, 208.5, 187.5);
}
.form-range::-webkit-slider-runnable-track {
width: 100%;
@ -2510,7 +2510,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #fbd1bc;
background-color: rgb(250.8, 208.5, 187.5);
}
.form-range::-moz-range-track {
width: 100%;
@ -2833,7 +2833,7 @@ textarea.form-control-lg {
.was-validated .form-control:invalid, .form-control.is-invalid {
border-color: var(--bs-form-invalid-border-color);
padding-right: calc(1.5em + 0.75rem);
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23873208'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23873208' stroke='none'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29' stroke='none'/%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right calc(0.375em + 0.1875rem) center;
background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@ -2852,7 +2852,7 @@ textarea.form-control-lg {
border-color: var(--bs-form-invalid-border-color);
}
.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] {
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23873208'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23873208' stroke='none'/%3e%3c/svg%3e");
--bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='rgb%28135.0941422594, 50.4351464435, 8.4058577406%29' stroke='none'/%3e%3c/svg%3e");
padding-right: 4.125rem;
background-position: right 0.75rem center, center right 2.25rem;
background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
@ -2958,6 +2958,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2971,12 +2974,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f1641e;
--bs-btn-border-color: #f1641e;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #cd551a;
--bs-btn-hover-border-color: #c15018;
--bs-btn-hover-bg: rgb(204.85, 85, 25.5);
--bs-btn-hover-border-color: rgb(192.8, 80, 24);
--bs-btn-focus-shadow-rgb: 243, 123, 64;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #c15018;
--bs-btn-active-border-color: #b54b17;
--bs-btn-active-bg: rgb(192.8, 80, 24);
--bs-btn-active-border-color: rgb(180.75, 75, 22.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #f1641e;
@ -2988,12 +2991,12 @@ textarea.form-control-lg {
--bs-btn-bg: #00a846;
--bs-btn-border-color: #00a846;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #008f3c;
--bs-btn-hover-border-color: #008638;
--bs-btn-hover-bg: rgb(0, 142.8, 59.5);
--bs-btn-hover-border-color: rgb(0, 134.4, 56);
--bs-btn-focus-shadow-rgb: 38, 181, 98;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #008638;
--bs-btn-active-border-color: #007e35;
--bs-btn-active-bg: rgb(0, 134.4, 56);
--bs-btn-active-border-color: rgb(0, 126, 52.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #00a846;
@ -3005,12 +3008,12 @@ textarea.form-control-lg {
--bs-btn-bg: #6610f2;
--bs-btn-border-color: #6610f2;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #570ece;
--bs-btn-hover-border-color: #520dc2;
--bs-btn-hover-bg: rgb(86.7, 13.6, 205.7);
--bs-btn-hover-border-color: rgb(81.6, 12.8, 193.6);
--bs-btn-focus-shadow-rgb: 125, 52, 244;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #520dc2;
--bs-btn-active-border-color: #4d0cb6;
--bs-btn-active-bg: rgb(81.6, 12.8, 193.6);
--bs-btn-active-border-color: rgb(76.5, 12, 181.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #6610f2;
@ -3022,12 +3025,12 @@ textarea.form-control-lg {
--bs-btn-bg: #007bff;
--bs-btn-border-color: #007bff;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #0069d9;
--bs-btn-hover-border-color: #0062cc;
--bs-btn-hover-bg: rgb(0, 104.55, 216.75);
--bs-btn-hover-border-color: rgb(0, 98.4, 204);
--bs-btn-focus-shadow-rgb: 38, 143, 255;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #0062cc;
--bs-btn-active-border-color: #005cbf;
--bs-btn-active-bg: rgb(0, 98.4, 204);
--bs-btn-active-border-color: rgb(0, 92.25, 191.25);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #007bff;
@ -3039,12 +3042,12 @@ textarea.form-control-lg {
--bs-btn-bg: #ffc107;
--bs-btn-border-color: #ffc107;
--bs-btn-hover-color: #222;
--bs-btn-hover-bg: #ffca2c;
--bs-btn-hover-border-color: #ffc720;
--bs-btn-hover-bg: rgb(255, 202.3, 44.2);
--bs-btn-hover-border-color: rgb(255, 199.2, 31.8);
--bs-btn-focus-shadow-rgb: 222, 169, 11;
--bs-btn-active-color: #222;
--bs-btn-active-bg: #ffcd39;
--bs-btn-active-border-color: #ffc720;
--bs-btn-active-bg: rgb(255, 205.4, 56.6);
--bs-btn-active-border-color: rgb(255, 199.2, 31.8);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #222;
--bs-btn-disabled-bg: #ffc107;
@ -3053,19 +3056,19 @@ textarea.form-control-lg {
.btn-danger {
--bs-btn-color: #fff;
--bs-btn-bg: #873208;
--bs-btn-border-color: #873208;
--bs-btn-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #732b07;
--bs-btn-hover-border-color: #6c2806;
--bs-btn-hover-bg: rgb(114.8300209205, 42.869874477, 7.1449790795);
--bs-btn-hover-border-color: rgb(108.0753138075, 40.3481171548, 6.7246861925);
--bs-btn-focus-shadow-rgb: 153, 81, 45;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #6c2806;
--bs-btn-active-border-color: #652606;
--bs-btn-active-bg: rgb(108.0753138075, 40.3481171548, 6.7246861925);
--bs-btn-active-border-color: rgb(101.3206066946, 37.8263598326, 6.3043933054);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #873208;
--bs-btn-disabled-border-color: #873208;
--bs-btn-disabled-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-disabled-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
}
.btn-light {
@ -3073,12 +3076,12 @@ textarea.form-control-lg {
--bs-btn-bg: #f8f9fa;
--bs-btn-border-color: #f8f9fa;
--bs-btn-hover-color: #222;
--bs-btn-hover-bg: #d3d4d5;
--bs-btn-hover-border-color: #c6c7c8;
--bs-btn-hover-bg: rgb(210.8, 211.65, 212.5);
--bs-btn-hover-border-color: rgb(198.4, 199.2, 200);
--bs-btn-focus-shadow-rgb: 216, 217, 218;
--bs-btn-active-color: #222;
--bs-btn-active-bg: #c6c7c8;
--bs-btn-active-border-color: #babbbc;
--bs-btn-active-bg: rgb(198.4, 199.2, 200);
--bs-btn-active-border-color: rgb(186, 186.75, 187.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #222;
--bs-btn-disabled-bg: #f8f9fa;
@ -3090,12 +3093,12 @@ textarea.form-control-lg {
--bs-btn-bg: #212529;
--bs-btn-border-color: #212529;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #424649;
--bs-btn-hover-border-color: #373b3e;
--bs-btn-hover-bg: rgb(66.3, 69.7, 73.1);
--bs-btn-hover-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-focus-shadow-rgb: 66, 70, 73;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #4d5154;
--bs-btn-active-border-color: #373b3e;
--bs-btn-active-bg: rgb(77.4, 80.6, 83.8);
--bs-btn-active-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #212529;
@ -3188,19 +3191,19 @@ textarea.form-control-lg {
}
.btn-outline-danger {
--bs-btn-color: #873208;
--bs-btn-border-color: #873208;
--bs-btn-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #873208;
--bs-btn-hover-border-color: #873208;
--bs-btn-hover-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-hover-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-focus-shadow-rgb: 135, 50, 8;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #873208;
--bs-btn-active-border-color: #873208;
--bs-btn-active-bg: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-active-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-active-shadow: inset 0 3px 5px rgba(34, 34, 34, 0.125);
--bs-btn-disabled-color: #873208;
--bs-btn-disabled-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: #873208;
--bs-btn-disabled-border-color: rgb(135.0941422594, 50.4351464435, 8.4058577406);
--bs-gradient: none;
}
@ -4486,12 +4489,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23495057' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2360280c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #f8b28f;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%2896.4, 40, 12%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(241, 100, 30, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4549,7 +4551,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4567,7 +4568,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4578,11 +4579,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4591,27 +4592,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f7a278'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23f7a278'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28246.6, 162, 120%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28246.6, 162, 120%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -5485,7 +5486,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6050,20 +6050,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6683,14 +6675,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {
@ -6845,8 +6834,8 @@ textarea.form-control-lg {
text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important;
}
.link-danger:hover, .link-danger:focus {
color: RGBA(108, 40, 6, var(--bs-link-opacity, 1)) !important;
text-decoration-color: RGBA(108, 40, 6, var(--bs-link-underline-opacity, 1)) !important;
color: RGBA(108, 40, 7, var(--bs-link-opacity, 1)) !important;
text-decoration-color: RGBA(108, 40, 7, var(--bs-link-underline-opacity, 1)) !important;
}
.link-light {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -45,27 +45,27 @@
--bs-danger-rgb: 255, 95, 110;
--bs-light-rgb: 68, 68, 68;
--bs-dark-rgb: 235, 235, 235;
--bs-primary-text-emphasis: #661a4a;
--bs-secondary-text-emphasis: #005266;
--bs-success-text-emphasis: #026640;
--bs-info-text-emphasis: #005266;
--bs-warning-text-emphasis: #66643c;
--bs-primary-text-emphasis: rgb(102, 25.6, 74.4);
--bs-secondary-text-emphasis: rgb(0.4, 82, 101.6);
--bs-success-text-emphasis: rgb(2, 102, 64.4);
--bs-info-text-emphasis: rgb(0.4, 82, 101.6);
--bs-warning-text-emphasis: rgb(102, 100.4, 60);
--bs-danger-text-emphasis: #66262c;
--bs-light-text-emphasis: #444;
--bs-dark-text-emphasis: #444;
--bs-primary-bg-subtle: #ffd9f1;
--bs-secondary-bg-subtle: #ccf5ff;
--bs-success-bg-subtle: #cdffec;
--bs-info-bg-subtle: #ccf5ff;
--bs-warning-bg-subtle: #fffeea;
--bs-primary-bg-subtle: rgb(255, 216.8, 241.2);
--bs-secondary-bg-subtle: rgb(204.2, 245, 254.8);
--bs-success-bg-subtle: rgb(205, 255, 236.2);
--bs-info-bg-subtle: rgb(204.2, 245, 254.8);
--bs-warning-bg-subtle: rgb(255, 254.2, 234);
--bs-danger-bg-subtle: #ffdfe2;
--bs-light-bg-subtle: #fcfcfd;
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #ffb3e3;
--bs-secondary-border-subtle: #99ebff;
--bs-success-border-subtle: #9bffd9;
--bs-info-border-subtle: #99ebff;
--bs-warning-border-subtle: #fffdd5;
--bs-primary-border-subtle: rgb(255, 178.6, 227.4);
--bs-secondary-border-subtle: rgb(153.4, 235, 254.6);
--bs-success-border-subtle: rgb(155, 255, 217.4);
--bs-info-border-subtle: rgb(153.4, 235, 254.6);
--bs-warning-border-subtle: rgb(255, 253.4, 213);
--bs-danger-border-subtle: #ffbfc5;
--bs-light-border-subtle: #ebebeb;
--bs-dark-border-subtle: #adb5bd;
@ -97,7 +97,7 @@
--bs-link-color: rgb(255, 64, 186);
--bs-link-color-rgb: 255, 64, 186;
--bs-link-decoration: none;
--bs-link-hover-color: #cc3395;
--bs-link-hover-color: rgb(204, 51.2, 148.8);
--bs-link-hover-color-rgb: 204, 51, 149;
--bs-code-color: rgb(255, 64, 186);
--bs-highlight-color: #ebebeb;
@ -142,42 +142,42 @@
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #292929;
--bs-tertiary-bg-rgb: 41, 41, 41;
--bs-primary-text-emphasis: #ff8cd6;
--bs-secondary-text-emphasis: #67e1fe;
--bs-success-text-emphasis: #69ffc7;
--bs-info-text-emphasis: #67e1fe;
--bs-warning-text-emphasis: #fffdc0;
--bs-primary-text-emphasis: rgb(255, 140.4, 213.6);
--bs-secondary-text-emphasis: rgb(102.6, 225, 254.4);
--bs-success-text-emphasis: rgb(105, 255, 198.6);
--bs-info-text-emphasis: rgb(102.6, 225, 254.4);
--bs-warning-text-emphasis: rgb(255, 252.6, 192);
--bs-danger-text-emphasis: #ff9fa8;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #330d25;
--bs-secondary-bg-subtle: #002933;
--bs-success-bg-subtle: #013320;
--bs-info-bg-subtle: #002933;
--bs-warning-bg-subtle: #33321e;
--bs-primary-bg-subtle: rgb(51, 12.8, 37.2);
--bs-secondary-bg-subtle: rgb(0.2, 41, 50.8);
--bs-success-bg-subtle: rgb(1, 51, 32.2);
--bs-info-bg-subtle: rgb(0.2, 41, 50.8);
--bs-warning-bg-subtle: rgb(51, 50.2, 30);
--bs-danger-bg-subtle: #331316;
--bs-light-bg-subtle: #303030;
--bs-dark-bg-subtle: #181818;
--bs-primary-border-subtle: #992670;
--bs-secondary-border-subtle: #017b98;
--bs-success-border-subtle: #039961;
--bs-info-border-subtle: #017b98;
--bs-warning-border-subtle: #99975a;
--bs-primary-border-subtle: rgb(153, 38.4, 111.6);
--bs-secondary-border-subtle: rgb(0.6, 123, 152.4);
--bs-success-border-subtle: rgb(3, 153, 96.6);
--bs-info-border-subtle: rgb(0.6, 123, 152.4);
--bs-warning-border-subtle: rgb(153, 150.6, 90);
--bs-danger-border-subtle: #993942;
--bs-light-border-subtle: #444;
--bs-dark-border-subtle: #303030;
--bs-heading-color: inherit;
--bs-link-color: #ff8cd6;
--bs-link-hover-color: #ffa3de;
--bs-link-color: rgb(255, 140.4, 213.6);
--bs-link-hover-color: rgb(255, 163.32, 221.88);
--bs-link-color-rgb: 255, 140, 214;
--bs-link-hover-color-rgb: 255, 163, 222;
--bs-code-color: #ff8cd6;
--bs-code-color: rgb(255, 140.4, 213.6);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #888;
--bs-border-color: #444;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #69ffc7;
--bs-form-valid-border-color: #69ffc7;
--bs-form-valid-color: rgb(105, 255, 198.6);
--bs-form-valid-border-color: rgb(105, 255, 198.6);
--bs-form-invalid-color: #ff9fa8;
--bs-form-invalid-border-color: #ff9fa8;
}
@ -1936,13 +1936,13 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #ffd9f1;
--bs-table-border-color: #ccaec1;
--bs-table-striped-bg: #f2cee5;
--bs-table-bg: rgb(255, 216.8, 241.2);
--bs-table-border-color: rgb(204, 173.44, 192.96);
--bs-table-striped-bg: rgb(242.25, 205.96, 229.14);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6c3d9;
--bs-table-active-bg: rgb(229.5, 195.12, 217.08);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ecc9df;
--bs-table-hover-bg: rgb(235.875, 200.54, 223.11);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1950,13 +1950,13 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #ccf5ff;
--bs-table-border-color: #a3c4cc;
--bs-table-striped-bg: #c2e9f2;
--bs-table-bg: rgb(204.2, 245, 254.8);
--bs-table-border-color: rgb(163.36, 196, 203.84);
--bs-table-striped-bg: rgb(193.99, 232.75, 242.06);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dde6;
--bs-table-active-bg: rgb(183.78, 220.5, 229.32);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde3ec;
--bs-table-hover-bg: rgb(188.885, 226.625, 235.69);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1964,13 +1964,13 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #cdffec;
--bs-table-border-color: #a4ccbd;
--bs-table-striped-bg: #c3f2e0;
--bs-table-bg: rgb(205, 255, 236.2);
--bs-table-border-color: rgb(164, 204, 188.96);
--bs-table-striped-bg: rgb(194.75, 242.25, 224.39);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b9e6d4;
--bs-table-active-bg: rgb(184.5, 229.5, 212.58);
--bs-table-active-color: #000;
--bs-table-hover-bg: #beecda;
--bs-table-hover-bg: rgb(189.625, 235.875, 218.485);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1978,13 +1978,13 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #ccf5ff;
--bs-table-border-color: #a3c4cc;
--bs-table-striped-bg: #c2e9f2;
--bs-table-bg: rgb(204.2, 245, 254.8);
--bs-table-border-color: rgb(163.36, 196, 203.84);
--bs-table-striped-bg: rgb(193.99, 232.75, 242.06);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dde6;
--bs-table-active-bg: rgb(183.78, 220.5, 229.32);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde3ec;
--bs-table-hover-bg: rgb(188.885, 226.625, 235.69);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1992,13 +1992,13 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fffeea;
--bs-table-border-color: #cccbbb;
--bs-table-striped-bg: #f2f1de;
--bs-table-bg: rgb(255, 254.2, 234);
--bs-table-border-color: rgb(204, 203.36, 187.2);
--bs-table-striped-bg: rgb(242.25, 241.49, 222.3);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6e5d3;
--bs-table-active-bg: rgb(229.5, 228.78, 210.6);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ecebd8;
--bs-table-hover-bg: rgb(235.875, 235.135, 216.45);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2007,12 +2007,12 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #ffdfe2;
--bs-table-border-color: #ccb2b5;
--bs-table-striped-bg: #f2d4d7;
--bs-table-border-color: rgb(204, 178.4, 180.8);
--bs-table-striped-bg: rgb(242.25, 211.85, 214.7);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6c9cb;
--bs-table-active-bg: rgb(229.5, 200.7, 203.4);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ecced1;
--bs-table-hover-bg: rgb(235.875, 206.275, 209.05);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2021,12 +2021,12 @@ progress {
.table-light {
--bs-table-color: #fff;
--bs-table-bg: #444;
--bs-table-border-color: dimgray;
--bs-table-striped-bg: #4d4d4d;
--bs-table-border-color: rgb(105.4, 105.4, 105.4);
--bs-table-striped-bg: rgb(77.35, 77.35, 77.35);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #575757;
--bs-table-active-bg: rgb(86.7, 86.7, 86.7);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #525252;
--bs-table-hover-bg: rgb(82.025, 82.025, 82.025);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2036,11 +2036,11 @@ progress {
--bs-table-color: #000;
--bs-table-bg: #ebebeb;
--bs-table-border-color: #bcbcbc;
--bs-table-striped-bg: #dfdfdf;
--bs-table-striped-bg: rgb(223.25, 223.25, 223.25);
--bs-table-striped-color: #000;
--bs-table-active-bg: #d4d4d4;
--bs-table-active-bg: rgb(211.5, 211.5, 211.5);
--bs-table-active-color: #000;
--bs-table-hover-bg: #d9d9d9;
--bs-table-hover-bg: rgb(217.375, 217.375, 217.375);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2141,7 +2141,7 @@ progress {
.form-control:focus {
color: #fff;
background-color: #888;
border-color: #ffa0dd;
border-color: rgb(255, 159.5, 220.5);
outline: 0;
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
}
@ -2288,7 +2288,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #ffa0dd;
border-color: rgb(255, 159.5, 220.5);
outline: 0;
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
}
@ -2372,7 +2372,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #ffa0dd;
border-color: rgb(255, 159.5, 220.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
}
@ -2419,7 +2419,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffa0dd'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28255, 159.5, 220.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2491,7 +2491,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #ffc6ea;
background-color: rgb(255, 197.7, 234.3);
background-image: var(--bs-gradient);
}
.form-range::-webkit-slider-runnable-track {
@ -2521,7 +2521,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #ffc6ea;
background-color: rgb(255, 197.7, 234.3);
background-image: var(--bs-gradient);
}
.form-range::-moz-range-track {
@ -2763,7 +2763,7 @@ textarea.form-control-lg {
}
.was-validated .form-control:valid:focus, .form-control.is-valid:focus {
border-color: var(--bs-form-valid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
}
.was-validated textarea.form-control:valid, textarea.form-control.is-valid {
@ -2782,7 +2782,7 @@ textarea.form-control-lg {
}
.was-validated .form-select:valid:focus, .form-select.is-valid:focus {
border-color: var(--bs-form-valid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
}
.was-validated .form-control-color:valid, .form-control-color.is-valid {
@ -2853,7 +2853,7 @@ textarea.form-control-lg {
}
.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
border-color: var(--bs-form-invalid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}
.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {
@ -2872,7 +2872,7 @@ textarea.form-control-lg {
}
.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {
border-color: var(--bs-form-invalid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}
.was-validated .form-control-color:invalid, .form-control-color.is-invalid {
@ -2976,6 +2976,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-active-shadow), var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-active-shadow), var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2991,12 +2994,12 @@ textarea.form-control-lg {
--bs-btn-bg: rgb(255, 64, 186);
--bs-btn-border-color: rgb(255, 64, 186);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #d9369e;
--bs-btn-hover-border-color: #cc3395;
--bs-btn-hover-bg: rgb(216.75, 54.4, 158.1);
--bs-btn-hover-border-color: rgb(204, 51.2, 148.8);
--bs-btn-focus-shadow-rgb: 255, 93, 196;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #cc3395;
--bs-btn-active-border-color: #bf308c;
--bs-btn-active-bg: rgb(204, 51.2, 148.8);
--bs-btn-active-border-color: rgb(191.25, 48, 139.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: rgb(255, 64, 186);
@ -3008,12 +3011,12 @@ textarea.form-control-lg {
--bs-btn-bg: #01cdfe;
--bs-btn-border-color: #01cdfe;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #27d5fe;
--bs-btn-hover-border-color: #1ad2fe;
--bs-btn-hover-bg: rgb(39.1, 212.5, 254.15);
--bs-btn-hover-border-color: rgb(26.4, 210, 254.1);
--bs-btn-focus-shadow-rgb: 1, 174, 216;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #34d7fe;
--bs-btn-active-border-color: #1ad2fe;
--bs-btn-active-bg: rgb(51.8, 215, 254.2);
--bs-btn-active-border-color: rgb(26.4, 210, 254.1);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #01cdfe;
@ -3025,12 +3028,12 @@ textarea.form-control-lg {
--bs-btn-bg: #05ffa1;
--bs-btn-border-color: #05ffa1;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #2bffaf;
--bs-btn-hover-border-color: #1effaa;
--bs-btn-hover-bg: rgb(42.5, 255, 175.1);
--bs-btn-hover-border-color: rgb(30, 255, 170.4);
--bs-btn-focus-shadow-rgb: 4, 217, 137;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #37ffb4;
--bs-btn-active-border-color: #1effaa;
--bs-btn-active-bg: rgb(55, 255, 179.8);
--bs-btn-active-border-color: rgb(30, 255, 170.4);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #05ffa1;
@ -3042,12 +3045,12 @@ textarea.form-control-lg {
--bs-btn-bg: #01cdfe;
--bs-btn-border-color: #01cdfe;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #27d5fe;
--bs-btn-hover-border-color: #1ad2fe;
--bs-btn-hover-bg: rgb(39.1, 212.5, 254.15);
--bs-btn-hover-border-color: rgb(26.4, 210, 254.1);
--bs-btn-focus-shadow-rgb: 1, 174, 216;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #34d7fe;
--bs-btn-active-border-color: #1ad2fe;
--bs-btn-active-bg: rgb(51.8, 215, 254.2);
--bs-btn-active-border-color: rgb(26.4, 210, 254.1);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #01cdfe;
@ -3059,12 +3062,12 @@ textarea.form-control-lg {
--bs-btn-bg: #fffb96;
--bs-btn-border-color: #fffb96;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #fffca6;
--bs-btn-hover-border-color: #fffba1;
--bs-btn-hover-bg: rgb(255, 251.6, 165.75);
--bs-btn-hover-border-color: rgb(255, 251.4, 160.5);
--bs-btn-focus-shadow-rgb: 217, 213, 128;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #fffcab;
--bs-btn-active-border-color: #fffba1;
--bs-btn-active-bg: rgb(255, 251.8, 171);
--bs-btn-active-border-color: rgb(255, 251.4, 160.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #fffb96;
@ -3076,12 +3079,12 @@ textarea.form-control-lg {
--bs-btn-bg: rgb(255, 95, 110);
--bs-btn-border-color: rgb(255, 95, 110);
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #ff7784;
--bs-btn-hover-border-color: #ff6f7d;
--bs-btn-hover-bg: rgb(255, 119, 131.75);
--bs-btn-hover-border-color: rgb(255, 111, 124.5);
--bs-btn-focus-shadow-rgb: 217, 81, 94;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #ff7f8b;
--bs-btn-active-border-color: #ff6f7d;
--bs-btn-active-border-color: rgb(255, 111, 124.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: rgb(255, 95, 110);
@ -3093,11 +3096,11 @@ textarea.form-control-lg {
--bs-btn-bg: #444;
--bs-btn-border-color: #444;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #3a3a3a;
--bs-btn-hover-border-color: #363636;
--bs-btn-hover-bg: rgb(57.8, 57.8, 57.8);
--bs-btn-hover-border-color: rgb(54.4, 54.4, 54.4);
--bs-btn-focus-shadow-rgb: 96, 96, 96;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #363636;
--bs-btn-active-bg: rgb(54.4, 54.4, 54.4);
--bs-btn-active-border-color: #333333;
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
@ -4527,12 +4530,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ebebeb'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23ebebeb' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23661a4a'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #ffa0dd;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%28102, 25.6, 74.4%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4590,7 +4592,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4608,7 +4609,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4619,11 +4620,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4632,27 +4633,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ff8cd6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ff8cd6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28255, 140.4, 213.6%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28255, 140.4, 213.6%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -5530,7 +5531,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6098,20 +6098,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6737,14 +6729,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {

View file

@ -1,7 +1,7 @@
@charset "UTF-8";
/*!
* Bootstrap v5.3.2 (https://getbootstrap.com/)
* Copyright 2011-2023 The Bootstrap Authors
* Bootstrap v5.3.3 (https://getbootstrap.com/)
* Copyright 2011-2024 The Bootstrap Authors
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
:root,
@ -35,7 +35,7 @@
--bs-info: #01cdfe;
--bs-warning: #fffb96;
--bs-danger: rgb(255, 95, 110);
--bs-light: #dadee3;
--bs-light: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-dark: #212529;
--bs-primary-rgb: 255, 64, 186;
--bs-secondary-rgb: 1, 205, 254;
@ -45,27 +45,27 @@
--bs-danger-rgb: 255, 95, 110;
--bs-light-rgb: 218, 222, 227;
--bs-dark-rgb: 33, 37, 41;
--bs-primary-text-emphasis: #661a4a;
--bs-secondary-text-emphasis: #005266;
--bs-success-text-emphasis: #026640;
--bs-info-text-emphasis: #005266;
--bs-warning-text-emphasis: #66643c;
--bs-primary-text-emphasis: rgb(102, 25.6, 74.4);
--bs-secondary-text-emphasis: rgb(0.4, 82, 101.6);
--bs-success-text-emphasis: rgb(2, 102, 64.4);
--bs-info-text-emphasis: rgb(0.4, 82, 101.6);
--bs-warning-text-emphasis: rgb(102, 100.4, 60);
--bs-danger-text-emphasis: #66262c;
--bs-light-text-emphasis: #495057;
--bs-dark-text-emphasis: #495057;
--bs-primary-bg-subtle: #ffd9f1;
--bs-secondary-bg-subtle: #ccf5ff;
--bs-success-bg-subtle: #cdffec;
--bs-info-bg-subtle: #ccf5ff;
--bs-warning-bg-subtle: #fffeea;
--bs-primary-bg-subtle: rgb(255, 216.8, 241.2);
--bs-secondary-bg-subtle: rgb(204.2, 245, 254.8);
--bs-success-bg-subtle: rgb(205, 255, 236.2);
--bs-info-bg-subtle: rgb(204.2, 245, 254.8);
--bs-warning-bg-subtle: rgb(255, 254.2, 234);
--bs-danger-bg-subtle: #ffdfe2;
--bs-light-bg-subtle: #fcfcfd;
--bs-light-bg-subtle: rgb(251.5, 252, 252.5);
--bs-dark-bg-subtle: #ced4da;
--bs-primary-border-subtle: #ffb3e3;
--bs-secondary-border-subtle: #99ebff;
--bs-success-border-subtle: #9bffd9;
--bs-info-border-subtle: #99ebff;
--bs-warning-border-subtle: #fffdd5;
--bs-primary-border-subtle: rgb(255, 178.6, 227.4);
--bs-secondary-border-subtle: rgb(153.4, 235, 254.6);
--bs-success-border-subtle: rgb(155, 255, 217.4);
--bs-info-border-subtle: rgb(153.4, 235, 254.6);
--bs-warning-border-subtle: rgb(255, 253.4, 213);
--bs-danger-border-subtle: #ffbfc5;
--bs-light-border-subtle: #e9ecef;
--bs-dark-border-subtle: #adb5bd;
@ -97,11 +97,11 @@
--bs-link-color: rgb(255, 64, 186);
--bs-link-color-rgb: 255, 64, 186;
--bs-link-decoration: none;
--bs-link-hover-color: #cc3395;
--bs-link-hover-color: rgb(204, 51.2, 148.8);
--bs-link-hover-color-rgb: 204, 51, 149;
--bs-code-color: rgb(255, 64, 186);
--bs-highlight-color: #495057;
--bs-highlight-bg: #fffeea;
--bs-highlight-bg: rgb(255, 254.2, 234);
--bs-border-width: 1px;
--bs-border-style: solid;
--bs-border-color: #dee2e6;
@ -140,44 +140,44 @@
--bs-secondary-bg-rgb: 52, 58, 64;
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
--bs-tertiary-color-rgb: 222, 226, 230;
--bs-tertiary-bg: #2b3035;
--bs-tertiary-bg: rgb(42.5, 47.5, 52.5);
--bs-tertiary-bg-rgb: 43, 48, 53;
--bs-primary-text-emphasis: #ff8cd6;
--bs-secondary-text-emphasis: #67e1fe;
--bs-success-text-emphasis: #69ffc7;
--bs-info-text-emphasis: #67e1fe;
--bs-warning-text-emphasis: #fffdc0;
--bs-primary-text-emphasis: rgb(255, 140.4, 213.6);
--bs-secondary-text-emphasis: rgb(102.6, 225, 254.4);
--bs-success-text-emphasis: rgb(105, 255, 198.6);
--bs-info-text-emphasis: rgb(102.6, 225, 254.4);
--bs-warning-text-emphasis: rgb(255, 252.6, 192);
--bs-danger-text-emphasis: #ff9fa8;
--bs-light-text-emphasis: #f8f9fa;
--bs-dark-text-emphasis: #dee2e6;
--bs-primary-bg-subtle: #330d25;
--bs-secondary-bg-subtle: #002933;
--bs-success-bg-subtle: #013320;
--bs-info-bg-subtle: #002933;
--bs-warning-bg-subtle: #33321e;
--bs-primary-bg-subtle: rgb(51, 12.8, 37.2);
--bs-secondary-bg-subtle: rgb(0.2, 41, 50.8);
--bs-success-bg-subtle: rgb(1, 51, 32.2);
--bs-info-bg-subtle: rgb(0.2, 41, 50.8);
--bs-warning-bg-subtle: rgb(51, 50.2, 30);
--bs-danger-bg-subtle: #331316;
--bs-light-bg-subtle: #343a40;
--bs-dark-bg-subtle: #1a1d20;
--bs-primary-border-subtle: #992670;
--bs-secondary-border-subtle: #017b98;
--bs-success-border-subtle: #039961;
--bs-info-border-subtle: #017b98;
--bs-warning-border-subtle: #99975a;
--bs-primary-border-subtle: rgb(153, 38.4, 111.6);
--bs-secondary-border-subtle: rgb(0.6, 123, 152.4);
--bs-success-border-subtle: rgb(3, 153, 96.6);
--bs-info-border-subtle: rgb(0.6, 123, 152.4);
--bs-warning-border-subtle: rgb(153, 150.6, 90);
--bs-danger-border-subtle: #993942;
--bs-light-border-subtle: #495057;
--bs-dark-border-subtle: #343a40;
--bs-heading-color: inherit;
--bs-link-color: #ff8cd6;
--bs-link-hover-color: #ffa3de;
--bs-link-color: rgb(255, 140.4, 213.6);
--bs-link-hover-color: rgb(255, 163.32, 221.88);
--bs-link-color-rgb: 255, 140, 214;
--bs-link-hover-color-rgb: 255, 163, 222;
--bs-code-color: #ff8cd6;
--bs-code-color: rgb(255, 140.4, 213.6);
--bs-highlight-color: #dee2e6;
--bs-highlight-bg: #66643c;
--bs-highlight-bg: rgb(102, 100.4, 60);
--bs-border-color: #495057;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
--bs-form-valid-color: #69ffc7;
--bs-form-valid-border-color: #69ffc7;
--bs-form-valid-color: rgb(105, 255, 198.6);
--bs-form-valid-border-color: rgb(105, 255, 198.6);
--bs-form-invalid-color: #ff9fa8;
--bs-form-invalid-border-color: #ff9fa8;
}
@ -1935,13 +1935,13 @@ progress {
.table-primary {
--bs-table-color: #000;
--bs-table-bg: #ffd9f1;
--bs-table-border-color: #ccaec1;
--bs-table-striped-bg: #f2cee5;
--bs-table-bg: rgb(255, 216.8, 241.2);
--bs-table-border-color: rgb(204, 173.44, 192.96);
--bs-table-striped-bg: rgb(242.25, 205.96, 229.14);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6c3d9;
--bs-table-active-bg: rgb(229.5, 195.12, 217.08);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ecc9df;
--bs-table-hover-bg: rgb(235.875, 200.54, 223.11);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1949,13 +1949,13 @@ progress {
.table-secondary {
--bs-table-color: #000;
--bs-table-bg: #ccf5ff;
--bs-table-border-color: #a3c4cc;
--bs-table-striped-bg: #c2e9f2;
--bs-table-bg: rgb(204.2, 245, 254.8);
--bs-table-border-color: rgb(163.36, 196, 203.84);
--bs-table-striped-bg: rgb(193.99, 232.75, 242.06);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dde6;
--bs-table-active-bg: rgb(183.78, 220.5, 229.32);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde3ec;
--bs-table-hover-bg: rgb(188.885, 226.625, 235.69);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1963,13 +1963,13 @@ progress {
.table-success {
--bs-table-color: #000;
--bs-table-bg: #cdffec;
--bs-table-border-color: #a4ccbd;
--bs-table-striped-bg: #c3f2e0;
--bs-table-bg: rgb(205, 255, 236.2);
--bs-table-border-color: rgb(164, 204, 188.96);
--bs-table-striped-bg: rgb(194.75, 242.25, 224.39);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b9e6d4;
--bs-table-active-bg: rgb(184.5, 229.5, 212.58);
--bs-table-active-color: #000;
--bs-table-hover-bg: #beecda;
--bs-table-hover-bg: rgb(189.625, 235.875, 218.485);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1977,13 +1977,13 @@ progress {
.table-info {
--bs-table-color: #000;
--bs-table-bg: #ccf5ff;
--bs-table-border-color: #a3c4cc;
--bs-table-striped-bg: #c2e9f2;
--bs-table-bg: rgb(204.2, 245, 254.8);
--bs-table-border-color: rgb(163.36, 196, 203.84);
--bs-table-striped-bg: rgb(193.99, 232.75, 242.06);
--bs-table-striped-color: #000;
--bs-table-active-bg: #b8dde6;
--bs-table-active-bg: rgb(183.78, 220.5, 229.32);
--bs-table-active-color: #000;
--bs-table-hover-bg: #bde3ec;
--bs-table-hover-bg: rgb(188.885, 226.625, 235.69);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -1991,13 +1991,13 @@ progress {
.table-warning {
--bs-table-color: #000;
--bs-table-bg: #fffeea;
--bs-table-border-color: #cccbbb;
--bs-table-striped-bg: #f2f1de;
--bs-table-bg: rgb(255, 254.2, 234);
--bs-table-border-color: rgb(204, 203.36, 187.2);
--bs-table-striped-bg: rgb(242.25, 241.49, 222.3);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6e5d3;
--bs-table-active-bg: rgb(229.5, 228.78, 210.6);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ecebd8;
--bs-table-hover-bg: rgb(235.875, 235.135, 216.45);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2006,12 +2006,12 @@ progress {
.table-danger {
--bs-table-color: #000;
--bs-table-bg: #ffdfe2;
--bs-table-border-color: #ccb2b5;
--bs-table-striped-bg: #f2d4d7;
--bs-table-border-color: rgb(204, 178.4, 180.8);
--bs-table-striped-bg: rgb(242.25, 211.85, 214.7);
--bs-table-striped-color: #000;
--bs-table-active-bg: #e6c9cb;
--bs-table-active-bg: rgb(229.5, 200.7, 203.4);
--bs-table-active-color: #000;
--bs-table-hover-bg: #ecced1;
--bs-table-hover-bg: rgb(235.875, 206.275, 209.05);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2019,13 +2019,13 @@ progress {
.table-light {
--bs-table-color: #000;
--bs-table-bg: #dadee3;
--bs-table-border-color: #aeb2b6;
--bs-table-striped-bg: #cfd3d8;
--bs-table-bg: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-table-border-color: rgb(174.1179310345, 177.74, 181.3620689655);
--bs-table-striped-bg: rgb(206.7650431034, 211.06625, 215.3674568966);
--bs-table-striped-color: #000;
--bs-table-active-bg: #c4c8cc;
--bs-table-active-bg: rgb(195.8826724138, 199.9575, 204.0323275862);
--bs-table-active-color: #000;
--bs-table-hover-bg: #cacdd2;
--bs-table-hover-bg: rgb(201.3238577586, 205.511875, 209.6998922414);
--bs-table-hover-color: #000;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2034,12 +2034,12 @@ progress {
.table-dark {
--bs-table-color: #fff;
--bs-table-bg: #212529;
--bs-table-border-color: #4d5154;
--bs-table-striped-bg: #2c3034;
--bs-table-border-color: rgb(77.4, 80.6, 83.8);
--bs-table-striped-bg: rgb(44.1, 47.9, 51.7);
--bs-table-striped-color: #fff;
--bs-table-active-bg: #373b3e;
--bs-table-active-bg: rgb(55.2, 58.8, 62.4);
--bs-table-active-color: #fff;
--bs-table-hover-bg: #323539;
--bs-table-hover-bg: rgb(49.65, 53.35, 57.05);
--bs-table-hover-color: #fff;
color: var(--bs-table-color);
border-color: var(--bs-table-border-color);
@ -2140,7 +2140,7 @@ progress {
.form-control:focus {
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
border-color: #ffa0dd;
border-color: rgb(255, 159.5, 220.5);
outline: 0;
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
}
@ -2287,7 +2287,7 @@ textarea.form-control-lg {
}
}
.form-select:focus {
border-color: #ffa0dd;
border-color: rgb(255, 159.5, 220.5);
outline: 0;
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
}
@ -2371,7 +2371,7 @@ textarea.form-control-lg {
filter: brightness(90%);
}
.form-check-input:focus {
border-color: #ffa0dd;
border-color: rgb(255, 159.5, 220.5);
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
}
@ -2418,7 +2418,7 @@ textarea.form-control-lg {
}
}
.form-switch .form-check-input:focus {
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23ffa0dd'/%3e%3c/svg%3e");
--bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgb%28255, 159.5, 220.5%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-position: right center;
@ -2490,7 +2490,7 @@ textarea.form-control-lg {
}
}
.form-range::-webkit-slider-thumb:active {
background-color: #ffc6ea;
background-color: rgb(255, 197.7, 234.3);
background-image: var(--bs-gradient);
}
.form-range::-webkit-slider-runnable-track {
@ -2520,7 +2520,7 @@ textarea.form-control-lg {
}
}
.form-range::-moz-range-thumb:active {
background-color: #ffc6ea;
background-color: rgb(255, 197.7, 234.3);
background-image: var(--bs-gradient);
}
.form-range::-moz-range-track {
@ -2762,7 +2762,7 @@ textarea.form-control-lg {
}
.was-validated .form-control:valid:focus, .form-control.is-valid:focus {
border-color: var(--bs-form-valid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
}
.was-validated textarea.form-control:valid, textarea.form-control.is-valid {
@ -2781,7 +2781,7 @@ textarea.form-control-lg {
}
.was-validated .form-select:valid:focus, .form-select.is-valid:focus {
border-color: var(--bs-form-valid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
}
.was-validated .form-control-color:valid, .form-control-color.is-valid {
@ -2852,7 +2852,7 @@ textarea.form-control-lg {
}
.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
border-color: var(--bs-form-invalid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}
.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {
@ -2871,7 +2871,7 @@ textarea.form-control-lg {
}
.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {
border-color: var(--bs-form-invalid-border-color);
box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
}
.was-validated .form-control-color:invalid, .form-control-color.is-invalid {
@ -2975,6 +2975,9 @@ textarea.form-control-lg {
.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
box-shadow: var(--bs-btn-active-shadow), var(--bs-btn-focus-box-shadow);
}
.btn-check:checked:focus-visible + .btn {
box-shadow: var(--bs-btn-active-shadow), var(--bs-btn-focus-box-shadow);
}
.btn:disabled, .btn.disabled, fieldset:disabled .btn {
color: var(--bs-btn-disabled-color);
pointer-events: none;
@ -2990,12 +2993,12 @@ textarea.form-control-lg {
--bs-btn-bg: rgb(255, 64, 186);
--bs-btn-border-color: rgb(255, 64, 186);
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #d9369e;
--bs-btn-hover-border-color: #cc3395;
--bs-btn-hover-bg: rgb(216.75, 54.4, 158.1);
--bs-btn-hover-border-color: rgb(204, 51.2, 148.8);
--bs-btn-focus-shadow-rgb: 255, 93, 196;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #cc3395;
--bs-btn-active-border-color: #bf308c;
--bs-btn-active-bg: rgb(204, 51.2, 148.8);
--bs-btn-active-border-color: rgb(191.25, 48, 139.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: rgb(255, 64, 186);
@ -3007,12 +3010,12 @@ textarea.form-control-lg {
--bs-btn-bg: #01cdfe;
--bs-btn-border-color: #01cdfe;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #27d5fe;
--bs-btn-hover-border-color: #1ad2fe;
--bs-btn-hover-bg: rgb(39.1, 212.5, 254.15);
--bs-btn-hover-border-color: rgb(26.4, 210, 254.1);
--bs-btn-focus-shadow-rgb: 1, 174, 216;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #34d7fe;
--bs-btn-active-border-color: #1ad2fe;
--bs-btn-active-bg: rgb(51.8, 215, 254.2);
--bs-btn-active-border-color: rgb(26.4, 210, 254.1);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #01cdfe;
@ -3024,12 +3027,12 @@ textarea.form-control-lg {
--bs-btn-bg: #05ffa1;
--bs-btn-border-color: #05ffa1;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #2bffaf;
--bs-btn-hover-border-color: #1effaa;
--bs-btn-hover-bg: rgb(42.5, 255, 175.1);
--bs-btn-hover-border-color: rgb(30, 255, 170.4);
--bs-btn-focus-shadow-rgb: 4, 217, 137;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #37ffb4;
--bs-btn-active-border-color: #1effaa;
--bs-btn-active-bg: rgb(55, 255, 179.8);
--bs-btn-active-border-color: rgb(30, 255, 170.4);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #05ffa1;
@ -3041,12 +3044,12 @@ textarea.form-control-lg {
--bs-btn-bg: #01cdfe;
--bs-btn-border-color: #01cdfe;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #27d5fe;
--bs-btn-hover-border-color: #1ad2fe;
--bs-btn-hover-bg: rgb(39.1, 212.5, 254.15);
--bs-btn-hover-border-color: rgb(26.4, 210, 254.1);
--bs-btn-focus-shadow-rgb: 1, 174, 216;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #34d7fe;
--bs-btn-active-border-color: #1ad2fe;
--bs-btn-active-bg: rgb(51.8, 215, 254.2);
--bs-btn-active-border-color: rgb(26.4, 210, 254.1);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #01cdfe;
@ -3058,12 +3061,12 @@ textarea.form-control-lg {
--bs-btn-bg: #fffb96;
--bs-btn-border-color: #fffb96;
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #fffca6;
--bs-btn-hover-border-color: #fffba1;
--bs-btn-hover-bg: rgb(255, 251.6, 165.75);
--bs-btn-hover-border-color: rgb(255, 251.4, 160.5);
--bs-btn-focus-shadow-rgb: 217, 213, 128;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #fffcab;
--bs-btn-active-border-color: #fffba1;
--bs-btn-active-bg: rgb(255, 251.8, 171);
--bs-btn-active-border-color: rgb(255, 251.4, 160.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #fffb96;
@ -3075,12 +3078,12 @@ textarea.form-control-lg {
--bs-btn-bg: rgb(255, 95, 110);
--bs-btn-border-color: rgb(255, 95, 110);
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #ff7784;
--bs-btn-hover-border-color: #ff6f7d;
--bs-btn-hover-bg: rgb(255, 119, 131.75);
--bs-btn-hover-border-color: rgb(255, 111, 124.5);
--bs-btn-focus-shadow-rgb: 217, 81, 94;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #ff7f8b;
--bs-btn-active-border-color: #ff6f7d;
--bs-btn-active-border-color: rgb(255, 111, 124.5);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: rgb(255, 95, 110);
@ -3089,19 +3092,19 @@ textarea.form-control-lg {
.btn-light {
--bs-btn-color: #000;
--bs-btn-bg: #dadee3;
--bs-btn-border-color: #dadee3;
--bs-btn-bg: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-border-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #b9bdc1;
--bs-btn-hover-border-color: #aeb2b6;
--bs-btn-hover-bg: rgb(185.0003017241, 188.84875, 192.6971982759);
--bs-btn-hover-border-color: rgb(174.1179310345, 177.74, 181.3620689655);
--bs-btn-focus-shadow-rgb: 185, 189, 193;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #aeb2b6;
--bs-btn-active-border-color: #a4a7aa;
--bs-btn-active-bg: rgb(174.1179310345, 177.74, 181.3620689655);
--bs-btn-active-border-color: rgb(163.2355603448, 166.63125, 170.0269396552);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #000;
--bs-btn-disabled-bg: #dadee3;
--bs-btn-disabled-border-color: #dadee3;
--bs-btn-disabled-bg: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-disabled-border-color: rgb(217.6474137931, 222.175, 226.7025862069);
}
.btn-dark {
@ -3109,12 +3112,12 @@ textarea.form-control-lg {
--bs-btn-bg: #212529;
--bs-btn-border-color: #212529;
--bs-btn-hover-color: #fff;
--bs-btn-hover-bg: #424649;
--bs-btn-hover-border-color: #373b3e;
--bs-btn-hover-bg: rgb(66.3, 69.7, 73.1);
--bs-btn-hover-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-focus-shadow-rgb: 66, 70, 73;
--bs-btn-active-color: #fff;
--bs-btn-active-bg: #4d5154;
--bs-btn-active-border-color: #373b3e;
--bs-btn-active-bg: rgb(77.4, 80.6, 83.8);
--bs-btn-active-border-color: rgb(55.2, 58.8, 62.4);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #fff;
--bs-btn-disabled-bg: #212529;
@ -3224,19 +3227,19 @@ textarea.form-control-lg {
}
.btn-outline-light {
--bs-btn-color: #dadee3;
--bs-btn-border-color: #dadee3;
--bs-btn-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-border-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-hover-color: #000;
--bs-btn-hover-bg: #dadee3;
--bs-btn-hover-border-color: #dadee3;
--bs-btn-hover-bg: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-hover-border-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-focus-shadow-rgb: 218, 222, 227;
--bs-btn-active-color: #000;
--bs-btn-active-bg: #dadee3;
--bs-btn-active-border-color: #dadee3;
--bs-btn-active-bg: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-active-border-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
--bs-btn-disabled-color: #dadee3;
--bs-btn-disabled-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-btn-disabled-bg: transparent;
--bs-btn-disabled-border-color: #dadee3;
--bs-btn-disabled-border-color: rgb(217.6474137931, 222.175, 226.7025862069);
--bs-gradient: none;
}
@ -4526,12 +4529,11 @@ textarea.form-control-lg {
--bs-accordion-btn-padding-y: 1rem;
--bs-accordion-btn-color: var(--bs-body-color);
--bs-accordion-btn-bg: var(--bs-accordion-bg);
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23495057' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-icon-width: 1.25rem;
--bs-accordion-btn-icon-transform: rotate(-180deg);
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23661a4a'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-border-color: #ffa0dd;
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='rgb%28102, 25.6, 74.4%29' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e");
--bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(255, 64, 186, 0.25);
--bs-accordion-body-padding-x: 1.25rem;
--bs-accordion-body-padding-y: 1rem;
@ -4589,7 +4591,6 @@ textarea.form-control-lg {
}
.accordion-button:focus {
z-index: 3;
border-color: var(--bs-accordion-btn-focus-border-color);
outline: 0;
box-shadow: var(--bs-accordion-btn-focus-box-shadow);
}
@ -4607,7 +4608,7 @@ textarea.form-control-lg {
border-top-left-radius: var(--bs-accordion-border-radius);
border-top-right-radius: var(--bs-accordion-border-radius);
}
.accordion-item:first-of-type .accordion-button {
.accordion-item:first-of-type > .accordion-header .accordion-button {
border-top-left-radius: var(--bs-accordion-inner-border-radius);
border-top-right-radius: var(--bs-accordion-inner-border-radius);
}
@ -4618,11 +4619,11 @@ textarea.form-control-lg {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
.accordion-item:last-of-type .accordion-button.collapsed {
.accordion-item:last-of-type > .accordion-header .accordion-button.collapsed {
border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
}
.accordion-item:last-of-type .accordion-collapse {
.accordion-item:last-of-type > .accordion-collapse {
border-bottom-right-radius: var(--bs-accordion-border-radius);
border-bottom-left-radius: var(--bs-accordion-border-radius);
}
@ -4631,27 +4632,27 @@ textarea.form-control-lg {
padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
}
.accordion-flush .accordion-collapse {
border-width: 0;
}
.accordion-flush .accordion-item {
.accordion-flush > .accordion-item {
border-right: 0;
border-left: 0;
border-radius: 0;
}
.accordion-flush .accordion-item:first-child {
.accordion-flush > .accordion-item:first-child {
border-top: 0;
}
.accordion-flush .accordion-item:last-child {
.accordion-flush > .accordion-item:last-child {
border-bottom: 0;
}
.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
.accordion-flush > .accordion-item > .accordion-header .accordion-button, .accordion-flush > .accordion-item > .accordion-header .accordion-button.collapsed {
border-radius: 0;
}
.accordion-flush > .accordion-item > .accordion-collapse {
border-radius: 0;
}
[data-bs-theme=dark] .accordion-button::after {
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ff8cd6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23ff8cd6'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28255, 140.4, 213.6%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='rgb%28255, 140.4, 213.6%29'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
}
.breadcrumb {
@ -5529,7 +5530,6 @@ textarea.form-control-lg {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: space-between;
padding: var(--bs-modal-header-padding);
border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
border-top-left-radius: var(--bs-modal-inner-border-radius);
@ -6097,20 +6097,12 @@ textarea.form-control-lg {
background-size: 100% 100%;
}
/* rtl:options: {
"autoRename": true,
"stringMap":[ {
"name" : "prev-next",
"search" : "prev",
"replace" : "next"
} ]
} */
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/;
}
.carousel-control-next-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/;
}
.carousel-indicators {
@ -6736,14 +6728,11 @@ textarea.form-control-lg {
.offcanvas-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
}
.offcanvas-header .btn-close {
padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto;
}
.offcanvas-title {
@ -6907,8 +6896,8 @@ textarea.form-control-lg {
text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important;
}
.link-light:hover, .link-light:focus {
color: RGBA(225, 229, 233, var(--bs-link-opacity, 1)) !important;
text-decoration-color: RGBA(225, 229, 233, var(--bs-link-underline-opacity, 1)) !important;
color: RGBA(225, 229, 232, var(--bs-link-opacity, 1)) !important;
text-decoration-color: RGBA(225, 229, 232, var(--bs-link-underline-opacity, 1)) !important;
}
.link-dark {

View file

@ -60,6 +60,10 @@
<symbol id="icon-add" viewBox="0 0 32 32" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<path d="M14.5,14.501l-10.502,0c-0.828,0 -1.5,0.673 -1.5,1.5c0,0.828 0.672,1.5 1.5,1.5l10.502,0l-0.001,10.502c0,0.828 0.672,1.5 1.5,1.501c0.828,-0 1.5,-0.673 1.5,-1.5l0.001,-10.503l10.502,0c0.828,0 1.5,-0.672 1.5,-1.5c0,-0.827 -0.672,-1.5 -1.5,-1.5l-10.502,0l0.001,-10.501c-0,-0.828 -0.672,-1.501 -1.5,-1.501c-0.828,0 -1.5,0.672 -1.5,1.5l-0.001,10.502Z"/>
</symbol>
<symbol id="icon-video" viewBox="0 0 256 256">
<rect width="256" height="256" fill="none"/>
<polygon points="160 112 112 80 112 144 160 112" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><rect x="32" y="48" width="192" height="128" rx="8" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><line x1="32" y1="208" x2="224" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/>
</symbol>
<symbol id="icon-play" viewBox="0 0 24 24">
<path d="M5.541 2.159c-0.153-0.1-0.34-0.159-0.541-0.159-0.552 0-1 0.448-1 1v18c-0.001 0.182 0.050 0.372 0.159 0.541 0.299 0.465 0.917 0.599 1.382 0.3l14-9c0.114-0.072 0.219-0.174 0.3-0.3 0.299-0.465 0.164-1.083-0.3-1.382zM6 4.832l11.151 7.168-11.151 7.168z"/>
</symbol>
@ -301,5 +305,8 @@
<symbol id="icon-history" viewBox="0 0 256 256">
<path d="M136,80v43.47l36.12,21.67a8,8,0,0,1-8.24,13.72l-40-24A8,8,0,0,1,120,128V80a8,8,0,0,1,16,0Zm-8-48A95.44,95.44,0,0,0,60.08,60.15C52.81,67.51,46.35,74.59,40,82V64a8,8,0,0,0-16,0v40a8,8,0,0,0,8,8H72a8,8,0,0,0,0-16H49c7.15-8.42,14.27-16.35,22.39-24.57a80,80,0,1,1,1.66,114.75,8,8,0,1,0-11,11.64A96,96,0,1,0,128,32Z" />
</symbol>
<symbol id="icon-caret-right" fill="currentColor" viewBox="0 0 256 256">
<path d="M181.66,133.66l-80,80a8,8,0,0,1-11.32-11.32L164.69,128,90.34,53.66a8,8,0,0,1,11.32-11.32l80,80A8,8,0,0,1,181.66,133.66Z" />
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View file

@ -5,6 +5,7 @@ import App from "../shared/components/app/app";
import { lazyHighlightjs } from "../shared/lazy-highlightjs";
import { loadUserLanguage } from "../shared/services/I18NextService";
import { verifyDynamicImports } from "../shared/dynamic-imports";
import { setupEmojiDataModel } from "../shared/markdown";
import "bootstrap/js/dist/collapse";
import "bootstrap/js/dist/dropdown";
@ -22,7 +23,7 @@ async function startClient() {
lazyHighlightjs.enableLazyLoading();
await loadUserLanguage();
await Promise.all([loadUserLanguage(), setupEmojiDataModel()]);
const wrapper = (
<BrowserRouter>

View file

@ -14,7 +14,7 @@ const themes: ReadonlyArray<string> = [
"litely-compact",
"vaporwave-dark",
"vaporwave-light",
"i386",
"i386-dark",
];
export async function buildThemeList(): Promise<ReadonlyArray<string>> {

View file

@ -33,22 +33,29 @@ export async function createSsrHtml(
);
if (!appleTouchIcon) {
appleTouchIcon = site?.site_view.site.icon
? `data:image/png;base64,${await sharp(
await fetchIconPng(site.site_view.site.icon),
)
.resize(180, 180)
.extend({
bottom: 20,
top: 20,
left: 20,
right: 20,
background: "#222222",
})
.png()
.toBuffer()
.then(buf => buf.toString("base64"))}`
: favIconPngUrl;
try {
appleTouchIcon = site?.site_view.site.icon
? `data:image/png;base64,${await sharp(
await fetchIconPng(site.site_view.site.icon),
)
.resize(180, 180)
.extend({
bottom: 20,
top: 20,
left: 20,
right: 20,
background: "#222222",
})
.png()
.toBuffer()
.then(buf => buf.toString("base64"))}`
: favIconPngUrl;
} catch {
console.log(
"Could not fetch site logo for apple touch icon. Using default icon.",
);
appleTouchIcon = favIconPngUrl;
}
}
const erudaStr =

View file

@ -1,19 +1,54 @@
import { readFile } from "fs/promises";
import { Site } from "lemmy-js-client";
import path from "path";
import { fetchIconPng } from "./fetch-icon-png";
import { getStaticDir } from "@utils/env";
type Icon = { sizes: string; src: string; type: string; purpose: string };
const iconSizes = [72, 96, 128, 144, 152, 192, 384, 512];
let icons: Icon[] | null = null;
const defaultLogoPathDirectory = path.join(
process.cwd(),
"dist",
"assets",
"icons",
);
function mapIcon(src: string, size: number): Icon {
return {
sizes: `${size}x${size}`,
type: "image/png",
src,
purpose: "any maskable",
};
}
function generateDefaultIcons() {
return iconSizes.map(size =>
mapIcon(`${getStaticDir()}/assets/icons/icon-${size}x${size}.png`, size),
);
}
export default async function (site: Site) {
const icon = site.icon ? await fetchIconPng(site.icon) : null;
if (!icons) {
try {
const icon = site.icon ? await fetchIconPng(site.icon) : null;
if (icon) {
icons = await Promise.all(
iconSizes.map(async size => {
const sharp = (await import("sharp")).default;
const src = `data:image/png:base64,${await sharp(icon)
.resize(size, size)
.png()
.toBuffer()
.then(buf => buf.toString("base64"))}`;
return mapIcon(src, size);
}),
);
} else {
icons = generateDefaultIcons();
}
} catch {
console.log(
`Failed to fetch site logo for manifest icon. Using default icon`,
);
icons = generateDefaultIcons();
}
}
return {
name: site.name,
@ -24,29 +59,7 @@ export default async function (site: Site) {
id: "/",
background_color: "#222222",
theme_color: "#222222",
icons: await Promise.all(
iconSizes.map(async size => {
let src = await readFile(
path.join(defaultLogoPathDirectory, `icon-${size}x${size}.png`),
).then(buf => buf.toString("base64"));
if (icon) {
const sharp = (await import("sharp")).default;
src = await sharp(icon)
.resize(size, size)
.png()
.toBuffer()
.then(buf => buf.toString("base64"));
}
return {
sizes: `${size}x${size}`,
type: "image/png",
src: `data:image/png;base64,${src}`,
purpose: "any maskable",
};
}),
),
icons,
shortcuts: [
{
name: "Search",

View file

@ -13,16 +13,18 @@ import { Navbar } from "./navbar";
import "./styles.scss";
import { Theme } from "./theme";
import AnonymousGuard from "../common/anonymous-guard";
import AdultConsentModal from "../common/adult-consent-modal";
import AdultConsentModal from "../common/modal/adult-consent-modal";
import { destroyTippy, setupTippy } from "../../tippy";
function handleJumpToContent(event) {
function handleJumpToContent(app: App, event: any) {
event.preventDefault();
app.contentRef.current?.focus();
}
export default class App extends Component<any, any> {
private isoData: IsoDataOptionalSite = setIsoData(this.context);
private readonly rootRef = createRef<HTMLDivElement>();
readonly contentRef = createRef<HTMLDivElement>();
componentDidMount() {
setupTippy(this.rootRef);
@ -32,6 +34,64 @@ export default class App extends Component<any, any> {
destroyTippy();
}
routes = routes.map(
({
path,
component: RouteComponent,
fetchInitialData,
getQueryParams,
mountedSameRouteNavKey,
}) => (
<Route
key={path}
path={path}
exact
component={routeProps => {
if (!fetchInitialData) {
FirstLoadService.falsify();
}
let queryProps = routeProps;
if (getQueryParams && this.isoData.site_res) {
// ErrorGuard will not render its children when
// site_res is missing, this guarantees that props
// will always contain the query params.
queryProps = {
...routeProps,
...getQueryParams(
routeProps.location.search,
this.isoData.site_res,
),
};
}
// When key is location.key the component will be recreated when
// navigating to itself. This is usesful to e.g. reset forms.
const key = mountedSameRouteNavKey ?? routeProps.location.key;
return (
<ErrorGuard>
<div tabIndex={-1} ref={this.contentRef}>
{RouteComponent &&
(isAuthPath(path ?? "") ? (
<AuthGuard {...routeProps}>
<RouteComponent key={key} {...queryProps} />
</AuthGuard>
) : isAnonymousPath(path ?? "") ? (
<AnonymousGuard>
<RouteComponent key={key} {...queryProps} />
</AnonymousGuard>
) : (
<RouteComponent key={key} {...queryProps} />
))}
</div>
</ErrorGuard>
);
}}
/>
),
);
render() {
const siteRes = this.isoData.site_res;
const siteView = siteRes?.site_view;
@ -64,58 +124,7 @@ export default class App extends Component<any, any> {
<Navbar siteRes={siteRes} />
<div className="mt-4 p-0 fl-1">
<Switch>
{routes.map(
({
path,
component: RouteComponent,
fetchInitialData,
getQueryParams,
}) => (
<Route
key={path}
path={path}
exact
component={routeProps => {
if (!fetchInitialData) {
FirstLoadService.falsify();
}
let queryProps = routeProps;
if (getQueryParams && this.isoData.site_res) {
// ErrorGuard will not render its children when
// site_res is missing, this guarantees that props
// will always contain the query params.
queryProps = {
...routeProps,
...getQueryParams(
routeProps.location.search,
this.isoData.site_res,
),
};
}
return (
<ErrorGuard>
<div tabIndex={-1}>
{RouteComponent &&
(isAuthPath(path ?? "") ? (
<AuthGuard {...routeProps}>
<RouteComponent {...queryProps} />
</AuthGuard>
) : isAnonymousPath(path ?? "") ? (
<AnonymousGuard>
<RouteComponent {...queryProps} />
</AnonymousGuard>
) : (
<RouteComponent {...queryProps} />
))}
</div>
</ErrorGuard>
);
}}
/>
),
)}
{this.routes}
<Route component={ErrorPage} />
</Switch>
</div>

View file

@ -63,7 +63,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
this.handleOutsideMenuClick = this.handleOutsideMenuClick.bind(this);
}
async componentDidMount() {
async componentWillMount() {
// Subscribe to jwt changes
if (isBrowser()) {
// On the first load, check the unreads

View file

@ -2,18 +2,23 @@ import { capitalizeFirstLetter } from "@utils/helpers";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import { CreateComment, EditComment, Language } from "lemmy-js-client";
import {
CommentResponse,
CreateComment,
EditComment,
Language,
} from "lemmy-js-client";
import { CommentNodeI } from "../../interfaces";
import { I18NextService, UserService } from "../../services";
import { Icon } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
import { RequestState } from "../../services/HttpService";
interface CommentFormProps {
/**
* Can either be the parent, or the editable comment. The right side is a postId.
*/
node: CommentNodeI | number;
finished?: boolean;
edit?: boolean;
disabled?: boolean;
focus?: boolean;
@ -21,7 +26,9 @@ interface CommentFormProps {
allLanguages: Language[];
siteLanguages: number[];
containerClass?: string;
onUpsertComment(form: EditComment | CreateComment): void;
onUpsertComment(
form: EditComment | CreateComment,
): Promise<RequestState<CommentResponse>>;
}
export class CommentForm extends Component<CommentFormProps, any> {
@ -50,7 +57,6 @@ export class CommentForm extends Component<CommentFormProps, any> {
initialContent={initialContent}
showLanguage
buttonTitle={this.buttonTitle}
finished={this.props.finished}
replyType={typeof this.props.node !== "number"}
focus={this.props.focus}
disabled={this.props.disabled}
@ -68,6 +74,9 @@ export class CommentForm extends Component<CommentFormProps, any> {
<Link className="alert-link" to="/login">
#
</Link>
<Link className="alert-link" to="/signup">
#
</Link>
</T>
</div>
)}
@ -83,33 +92,38 @@ export class CommentForm extends Component<CommentFormProps, any> {
: capitalizeFirstLetter(I18NextService.i18n.t("reply"));
}
handleCommentSubmit(content: string, language_id?: number) {
async handleCommentSubmit(
content: string,
language_id?: number,
): Promise<boolean> {
const { node, onUpsertComment, edit } = this.props;
let response: RequestState<CommentResponse>;
if (typeof node === "number") {
const post_id = node;
onUpsertComment({
response = await onUpsertComment({
content,
post_id,
language_id,
});
} else if (edit) {
const comment_id = node.comment_view.comment.id;
response = await onUpsertComment({
content,
comment_id,
language_id,
});
} else {
if (edit) {
const comment_id = node.comment_view.comment.id;
onUpsertComment({
content,
comment_id,
language_id,
});
} else {
const post_id = node.comment_view.post.id;
const parent_id = node.comment_view.comment.id;
this.props.onUpsertComment({
content,
parent_id,
post_id,
language_id,
});
}
const post_id = node.comment_view.post.id;
const parent_id = node.comment_view.comment.id;
response = await onUpsertComment({
content,
parent_id,
post_id,
language_id,
});
}
return response.state !== "failed";
}
}

View file

@ -2,7 +2,7 @@ import { colorList, getCommentParentId } from "@utils/app";
import { futureDaysToUnixTime, numToSI } from "@utils/helpers";
import classNames from "classnames";
import { isBefore, parseISO, subMinutes } from "date-fns";
import { Component, InfernoNode, linkEvent } from "inferno";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
import {
AddAdmin,
@ -33,7 +33,6 @@ import {
SaveComment,
TransferCommunity,
} from "lemmy-js-client";
import deepEqual from "lodash.isequal";
import { commentTreeMaxDepth } from "../../config";
import {
CommentNodeI,
@ -52,7 +51,7 @@ import { CommunityLink } from "../community/community-link";
import { PersonListing } from "../person/person-listing";
import { CommentForm } from "./comment-form";
import { CommentNodes } from "./comment-nodes";
import { BanUpdateForm } from "../common/mod-action-form-modal";
import { BanUpdateForm } from "../common/modal/mod-action-form-modal";
import CommentActionDropdown from "../common/content-actions/comment-action-dropdown";
import { RequestState } from "../../services/HttpService";
import { VoteDisplay } from "../common/vote-display";
@ -87,7 +86,6 @@ interface CommentNodeProps {
allLanguages: Language[];
siteLanguages: number[];
hideImages?: boolean;
finished: Map<CommentId, boolean | undefined>;
onSaveComment(form: SaveComment): Promise<void>;
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
@ -139,6 +137,8 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
super(props, context);
this.handleReplyCancel = this.handleReplyCancel.bind(this);
this.handleCreateComment = this.handleCreateComment.bind(this);
this.handleEditComment = this.handleEditComment.bind(this);
this.handleReportComment = this.handleReportComment.bind(this);
this.handleRemoveComment = this.handleRemoveComment.bind(this);
this.handleReplyClick = this.handleReplyClick.bind(this);
@ -164,22 +164,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
return this.commentView.comment.id;
}
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & CommentNodeProps>,
): void {
if (!deepEqual(this.props, nextProps)) {
this.setState({
showEdit: false,
showAdvanced: false,
createOrEditCommentLoading: false,
upvoteLoading: false,
downvoteLoading: false,
readLoading: false,
fetchChildrenLoading: false,
});
}
}
render() {
const node = this.props.node;
const cv = this.commentView;
@ -283,12 +267,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
edit
onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked}
finished={this.props.finished.get(id)}
focus
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
containerClass="comment-comment-container"
onUpsertComment={this.props.onEditComment}
onUpsertComment={this.handleEditComment}
/>
)}
{!this.state.showEdit && !this.state.collapsed && (
@ -311,7 +294,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
/>
)}
</div>
<div className="comment-bottom-btns d-flex justify-content-between justify-content-lg-start flex-wrap text-muted fw-bold mt-1">
<div className="comment-bottom-btns d-flex justify-content-start column-gap-1.5 flex-wrap text-muted fw-bold mt-1 align-items-center">
{this.props.showContext && this.getLinkButton()}
{this.props.markable && (
<button
@ -425,12 +408,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
node={node}
onReplyCancel={this.handleReplyCancel}
disabled={this.props.locked}
finished={this.props.finished.get(id)}
focus
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
containerClass="comment-comment-container"
onUpsertComment={this.props.onCreateComment}
onUpsertComment={this.handleCreateComment}
/>
)}
{!this.state.collapsed && node.children.length > 0 && (
@ -447,7 +429,6 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
hideImages={this.props.hideImages}
isChild={!this.props.isTopLevel}
depth={this.props.node.depth + 1}
finished={this.props.finished}
onCommentReplyRead={this.props.onCommentReplyRead}
onPersonMentionRead={this.props.onPersonMentionRead}
onCreateComment={this.props.onCreateComment}
@ -502,7 +483,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
<>
<Link
className={classnames}
to={`/comment/${
to={`/post/${cv.post.id}/${
(this.props.showContext && getCommentParentId(cv.comment)) ||
cv.comment.id
}`}
@ -559,6 +540,26 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
this.setState({ showReply: false, showEdit: false });
}
async handleCreateComment(
form: CreateComment,
): Promise<RequestState<CommentResponse>> {
const res = await this.props.onCreateComment(form);
if (res.state !== "failed") {
this.setState({ showReply: false, showEdit: false });
}
return res;
}
async handleEditComment(
form: EditComment,
): Promise<RequestState<CommentResponse>> {
const res = await this.props.onEditComment(form);
if (res.state !== "failed") {
this.setState({ showReply: false, showEdit: false });
}
return res;
}
isPersonMentionType(item: CommentNodeView): item is PersonMentionView {
return item.person_mention?.id !== undefined;
}
@ -636,7 +637,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
async handleBanFromCommunity({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id },
@ -645,10 +646,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
} = this.commentView;
const ban = !creator_banned_from_community;
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
@ -656,7 +656,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
community_id,
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});
@ -665,7 +665,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
async handleBanFromSite({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id, banned },
@ -675,14 +675,14 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
this.props.onBanPerson({
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});

View file

@ -7,7 +7,6 @@ import {
BanFromCommunity,
BanPerson,
BlockPerson,
CommentId,
CommentResponse,
CommunityModeratorView,
CreateComment,
@ -52,7 +51,6 @@ interface CommentNodesProps {
hideImages?: boolean;
isChild?: boolean;
depth?: number;
finished: Map<CommentId, boolean | undefined>;
onSaveComment(form: SaveComment): Promise<void>;
onCommentReplyRead(form: MarkCommentReplyAsRead): void;
onPersonMentionRead(form: MarkPersonMentionAsRead): void;
@ -124,7 +122,6 @@ export class CommentNodes extends Component<CommentNodesProps, any> {
hideImages={this.props.hideImages}
onCommentReplyRead={this.props.onCommentReplyRead}
onPersonMentionRead={this.props.onPersonMentionRead}
finished={this.props.finished}
onCreateComment={this.props.onCreateComment}
onEditComment={this.props.onEditComment}
onCommentVote={this.props.onCommentVote}

View file

@ -90,7 +90,6 @@ export class CommentReport extends Component<
siteLanguages={[]}
hideImages
// All of these are unused, since its viewonly
finished={new Map()}
onSaveComment={async () => {}}
onBlockPerson={async () => {}}
onDeleteComment={async () => {}}

View file

@ -1,30 +1,25 @@
import { Component } from "inferno";
import { UserService } from "../../services";
import { Spinner } from "./icon";
import { isBrowser } from "@utils/browser";
interface AnonymousGuardState {
hasRedirected: boolean;
}
class AnonymousGuard extends Component<any, AnonymousGuardState> {
state = {
hasRedirected: false,
} as AnonymousGuardState;
class AnonymousGuard extends Component<any, any> {
constructor(props: any, context: any) {
super(props, context);
}
componentDidMount() {
if (UserService.Instance.myUserInfo) {
hasAuth() {
return UserService.Instance.myUserInfo;
}
componentWillMount() {
if (this.hasAuth() && isBrowser()) {
this.context.router.history.replace(`/`);
} else {
this.setState({ hasRedirected: true });
}
}
render() {
return this.state.hasRedirected ? this.props.children : <Spinner />;
return !this.hasAuth() ? this.props.children : <Spinner />;
}
}

View file

@ -3,19 +3,12 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
import { UserService } from "../../services";
import { Spinner } from "./icon";
import { getQueryString } from "@utils/helpers";
interface AuthGuardState {
hasRedirected: boolean;
}
import { isBrowser } from "@utils/browser";
class AuthGuard extends Component<
RouteComponentProps<Record<string, string>>,
AuthGuardState
any
> {
state = {
hasRedirected: false,
} as AuthGuardState;
constructor(
props: RouteComponentProps<Record<string, string>>,
context: any,
@ -23,19 +16,21 @@ class AuthGuard extends Component<
super(props, context);
}
componentDidMount() {
if (!UserService.Instance.myUserInfo) {
hasAuth() {
return UserService.Instance.myUserInfo;
}
componentWillMount() {
if (!this.hasAuth() && isBrowser()) {
const { pathname, search } = this.props.location;
this.context.router.history.replace(
`/login${getQueryString({ prev: pathname + search })}`,
);
} else {
this.setState({ hasRedirected: true });
}
}
render() {
return this.state.hasRedirected ? this.props.children : <Spinner />;
return this.hasAuth() ? this.props.children : <Spinner />;
}
}

View file

@ -47,13 +47,13 @@ export class CommentSortSelect extends Component<
<option disabled aria-hidden="true">
{I18NextService.i18n.t("sort_type")}
</option>
<option value={"Hot"}>{I18NextService.i18n.t("hot")}</option>,
<option value={"Controversial"}>
<option value="Hot">{I18NextService.i18n.t("hot")}</option>
<option value="Controversial">
{I18NextService.i18n.t("controversial")}
</option>
<option value={"Top"}>{I18NextService.i18n.t("top")}</option>,
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
<option value="Top">{I18NextService.i18n.t("top")}</option>
<option value="New">{I18NextService.i18n.t("new")}</option>
<option value="Old">{I18NextService.i18n.t("old")}</option>
</select>
<a
className="sort-select-help text-muted"

View file

@ -14,9 +14,11 @@ import {
import ActionButton from "./action-button";
import classNames from "classnames";
import { Link } from "inferno-router";
import ConfirmationModal from "../confirmation-modal";
import ViewVotesModal from "../view-votes-modal";
import ModActionFormModal, { BanUpdateForm } from "../mod-action-form-modal";
import ConfirmationModal from "../modal/confirmation-modal";
import ViewVotesModal from "../modal/view-votes-modal";
import ModActionFormModal, {
BanUpdateForm,
} from "../modal/mod-action-form-modal";
import { BanType, CommentNodeView, PurgeType } from "../../../interfaces";
import { getApubName, hostname } from "@utils/helpers";
import { tippyMixin } from "../../mixins/tippy-mixin";
@ -159,6 +161,26 @@ export default class ContentActionDropdown extends Component<
(amMod(community.id) || (amAdmin() && community.local)) &&
!creator_banned_from_community;
const modHistoryUserTranslation = I18NextService.i18n.t(
"user_moderation_history",
{ user: creator.name },
);
// The link and translation string for the item
const { modHistoryItemLink, modHistoryItemTranslation } =
type === "post"
? {
modHistoryItemLink: `/modlog?postId=${id}`,
modHistoryItemTranslation: I18NextService.i18n.t(
"post_moderation_history",
),
}
: {
modHistoryItemLink: `/modlog?commentId=${id}`,
modHistoryItemTranslation: I18NextService.i18n.t(
"comment_moderation_history",
),
};
return (
<>
{type === "comment" && (
@ -349,15 +371,23 @@ export default class ContentActionDropdown extends Component<
<li>
<Link
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
to={`/modlog?${type === "post" ? "postId" : "commentId"}=${id}`}
title={I18NextService.i18n.t("moderation_history")}
aria-label={I18NextService.i18n.t("moderation_history")}
data-tippy-content={I18NextService.i18n.t(
"moderation_history",
)}
to={`/modlog?userId=${creator.id}`}
title={modHistoryUserTranslation}
aria-label={modHistoryUserTranslation}
data-tippy-content={modHistoryUserTranslation}
>
<Icon icon="history" inline classes="me-2" />
{I18NextService.i18n.t("moderation_history")}
{modHistoryUserTranslation}
</Link>
<Link
className="btn btn-link btn-sm d-flex align-items-center rounded-0 dropdown-item"
to={modHistoryItemLink}
title={modHistoryItemTranslation}
aria-label={modHistoryItemTranslation}
data-tippy-content={modHistoryItemTranslation}
>
<Icon icon="history" inline classes="me-2" />
{modHistoryItemTranslation}
</Link>
</li>
{(this.canMod || this.canAdmin) && (
@ -643,7 +673,6 @@ export default class ContentActionDropdown extends Component<
type,
} = this.props;
// Wait until componentDidMount runs (which only happens on the browser) to prevent sending over a gratuitous amount of markup
return (
<>
{renderRemoveDialog && (

View file

@ -13,8 +13,10 @@ interface EmojiPickerState {
showPicker: boolean;
}
function closeEmojiMartOnEsc(i, event): void {
event.key === "Escape" && i.setState({ showPicker: false });
function closeEmojiMartOnEsc(i: EmojiPicker, event: KeyboardEvent): void {
if (event.key === "Escape") {
i.setState({ showPicker: false });
}
}
@tippyMixin
@ -72,9 +74,11 @@ export class EmojiPicker extends Component<EmojiPickerProps, EmojiPickerState> {
e.preventDefault();
i.setState({ showPicker: !i.state.showPicker });
i.state.showPicker
? document.addEventListener("keyup", e => closeEmojiMartOnEsc(i, e))
: document.removeEventListener("keyup", e => closeEmojiMartOnEsc(i, e));
if (i.state.showPicker) {
document.addEventListener("keyup", e => closeEmojiMartOnEsc(i, e));
} else {
document.removeEventListener("keyup", e => closeEmojiMartOnEsc(i, e));
}
}
handleEmojiClick(e: any) {

View file

@ -77,8 +77,6 @@ export class ImageUploadForm extends Component<
i.setState({ loading: true });
HttpService.client.uploadImage({ image }).then(res => {
console.log("pictrs upload:");
console.log(res);
if (res.state === "success") {
if (res.data.msg === "ok") {
i.props.onUpload(res.data.url as string);

View file

@ -58,40 +58,6 @@ class PostsLoadingSkeletonItem extends Component<any, any> {
}
}
export class TrendingCommunitiesLoadingSkeleton extends Component<
LoadingSkeletonProps,
any
> {
render() {
return (
<div className="mb-2">
{Array.from({ length: this.props.itemCount ?? 10 }, (_, index) => (
<TrendingCommunitiesLoadingSkeletonItem key={index} />
))}
</div>
);
}
}
class TrendingCommunitiesLoadingSkeletonItem extends Component<any, any> {
render() {
return (
<div className="col flex-grow-1 mt-2 ps-2 pe-4">
<div className="row">
<div className="col flex-grow-0 pe-0">
<div className="d-flex placeholder-glow img-icon">
<span className="placeholder placeholder-lg w-100 h-100 rounded-circle" />
</div>
</div>
<div className="col flex-grow-1 pe-0">
<LoadingSkeletonLine size={12} />
</div>
</div>
</div>
);
}
}
export class CommentsLoadingSkeleton extends Component<any, any> {
render() {
return Array.from({ length: this.props.itemCount ?? 10 }, (_, index) => (

View file

@ -3,7 +3,7 @@ import { numToSI, randomStr } from "@utils/helpers";
import autosize from "autosize";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, InfernoNode, linkEvent } from "inferno";
import { Component, linkEvent } from "inferno";
import { Prompt } from "inferno-router";
import { Language } from "lemmy-js-client";
import {
@ -41,15 +41,15 @@ interface MarkdownTextAreaProps {
replyType?: boolean;
focus?: boolean;
disabled?: boolean;
finished?: boolean;
/**
* Whether to show the language selector
*/
showLanguage?: boolean;
hideNavigationWarnings?: boolean;
onContentChange?(val: string): void;
onContentBlur?(val: string): void;
onReplyCancel?(): void;
onSubmit?(content: string, languageId?: number): void;
onSubmit?(content: string, languageId?: number): Promise<boolean>;
allLanguages: Language[]; // TODO should probably be nullable
siteLanguages: number[]; // TODO same
}
@ -76,8 +76,6 @@ export class MarkdownTextArea extends Component<
private id = `markdown-textarea-${randomStr()}`;
private formId = `markdown-form-${randomStr()}`;
private tribute: any;
state: MarkdownTextAreaState = {
content: this.props.initialContent,
languageId: this.props.initialLanguageId,
@ -91,48 +89,26 @@ export class MarkdownTextArea extends Component<
this.handleLanguageChange = this.handleLanguageChange.bind(this);
this.handleEmoji = this.handleEmoji.bind(this);
}
async componentDidMount() {
if (isBrowser()) {
this.tribute = setupTribute();
}
}
componentDidMount() {
const textarea: any = document.getElementById(this.id);
if (textarea) {
autosize(textarea);
this.tribute.attach(textarea);
textarea.addEventListener("tribute-replaced", () => {
this.setState({ content: textarea.value });
autosize.update(textarea);
});
this.quoteInsert();
if (this.props.focus) {
textarea.focus();
}
}
}
componentWillReceiveProps(
nextProps: MarkdownTextAreaProps & { children?: InfernoNode },
) {
if (nextProps.finished) {
this.setState({
previewMode: false,
imageUploadStatus: undefined,
loading: false,
content: undefined,
});
if (this.props.replyType) {
this.props.onReplyCancel?.();
}
const tribute = await setupTribute();
const textarea: any = document.getElementById(this.id);
const form: any = document.getElementById(this.formId);
form.reset();
setTimeout(() => autosize.update(textarea), 10);
if (textarea) {
autosize(textarea);
tribute.attach(textarea);
textarea.addEventListener("tribute-replaced", () => {
this.setState({ content: textarea.value });
autosize.update(textarea);
});
this.quoteInsert();
if (this.props.focus) {
textarea.focus();
}
}
}
}
@ -149,8 +125,8 @@ export class MarkdownTextArea extends Component<
message={I18NextService.i18n.t("block_leaving")}
when={
!this.props.hideNavigationWarnings &&
!!this.state.content &&
!this.state.submitted
((!!this.state.content && !this.state.submitted) ||
this.state.loading)
}
/>
<div className="mb-3 row">
@ -237,6 +213,7 @@ export class MarkdownTextArea extends Component<
)}
value={this.state.content}
onInput={linkEvent(this, this.handleContentChange)}
onBlur={linkEvent(this, this.handleContentBlur)}
onPaste={linkEvent(this, this.handlePaste)}
onKeyDown={linkEvent(this, this.handleKeyBinds)}
required
@ -246,6 +223,7 @@ export class MarkdownTextArea extends Component<
this.props.maxLength ?? markdownFieldCharacterLimit
}
placeholder={this.props.placeholder}
spellCheck
/>
{this.state.previewMode && this.state.content && (
<div
@ -279,29 +257,14 @@ export class MarkdownTextArea extends Component<
</div>
<div className="col-12 d-flex align-items-center flex-wrap mt-2">
{this.props.showLanguage && (
<LanguageSelect
iconVersion
allLanguages={this.props.allLanguages}
selectedLanguageIds={
languageId ? Array.of(languageId) : undefined
}
siteLanguages={this.props.siteLanguages}
onChange={this.handleLanguageChange}
disabled={this.isDisabled}
/>
)}
{/* A flex expander */}
<div className="flex-grow-1"></div>
{this.props.replyType && (
{this.props.buttonTitle && (
<button
type="button"
type="submit"
className="btn btn-sm btn-secondary ms-2"
onClick={linkEvent(this, this.handleReplyCancel)}
disabled={this.isDisabled || !this.state.content}
>
{I18NextService.i18n.t("cancel")}
{this.state.loading && <Spinner className="me-1" />}
{this.props.buttonTitle}
</button>
)}
<button
@ -316,16 +279,31 @@ export class MarkdownTextArea extends Component<
? I18NextService.i18n.t("edit")
: I18NextService.i18n.t("preview")}
</button>
{this.props.buttonTitle && (
{this.props.replyType && (
<button
type="submit"
type="button"
className="btn btn-sm btn-secondary ms-2"
disabled={this.isDisabled || !this.state.content}
onClick={linkEvent(this, this.handleReplyCancel)}
>
{this.state.loading && <Spinner className="me-1" />}
{this.props.buttonTitle}
{I18NextService.i18n.t("cancel")}
</button>
)}
{/* A flex expander */}
<div className="flex-grow-1"></div>
{this.props.showLanguage && (
<LanguageSelect
iconVersion
allLanguages={this.props.allLanguages}
selectedLanguageIds={
languageId ? Array.of(languageId) : undefined
}
siteLanguages={this.props.siteLanguages}
onChange={this.handleLanguageChange}
disabled={this.isDisabled}
/>
)}
</div>
</div>
</form>
@ -471,7 +449,7 @@ export class MarkdownTextArea extends Component<
}));
}),
);
} catch (e) {
} catch {
errorOccurred = true;
}
}
@ -479,8 +457,6 @@ export class MarkdownTextArea extends Component<
async uploadSingleImage(i: MarkdownTextArea, image: File) {
const res = await HttpService.client.uploadImage({ image });
console.log("pictrs upload:");
console.log(res);
if (res.state === "success") {
if (res.data.msg === "ok") {
const imageMarkdown = `![](${res.data.url})`;
@ -518,6 +494,10 @@ export class MarkdownTextArea extends Component<
i.contentChange();
}
handleContentBlur(i: MarkdownTextArea, event: any) {
i.props.onContentBlur?.(event.target.value);
}
// Keybind handler
// Keybinds inspired by github comment area
handleKeyBinds(i: MarkdownTextArea, event: KeyboardEvent) {
@ -575,11 +555,15 @@ export class MarkdownTextArea extends Component<
this.setState({ languageId: val[0] });
}
handleSubmit(i: MarkdownTextArea, event: any) {
async handleSubmit(i: MarkdownTextArea, event: any) {
event.preventDefault();
if (i.state.content) {
i.setState({ loading: true, submitted: true });
i.props.onSubmit?.(i.state.content, i.state.languageId);
const success = await i.props.onSubmit?.(
i.state.content,
i.state.languageId,
);
i.setState({ loading: false, submitted: success ?? true });
}
}
@ -776,7 +760,9 @@ export class MarkdownTextArea extends Component<
getSelectedText(): string {
const { selectionStart: start, selectionEnd: end } =
document.getElementById(this.id) as any;
return start !== end ? this.state.content?.substring(start, end) ?? "" : "";
return start !== end
? (this.state.content?.substring(start, end) ?? "")
: "";
}
get isDisabled() {

View file

@ -1,10 +1,10 @@
import { Component, LinkedEvent, createRef, linkEvent } from "inferno";
import { modalMixin } from "../mixins/modal-mixin";
import { adultConsentCookieKey } from "../../config";
import { mdToHtml } from "../../markdown";
import { I18NextService } from "../../services";
import { modalMixin } from "../../mixins/modal-mixin";
import { adultConsentCookieKey } from "../../../config";
import { mdToHtml } from "../../../markdown";
import { I18NextService } from "../../../services";
import { isHttps } from "@utils/env";
import { IsoData } from "../../interfaces";
import { IsoData } from "../../../interfaces";
import { setIsoData } from "@utils/app";
interface AdultConsentModalProps {

View file

@ -1,20 +1,14 @@
import {
Component,
InfernoNode,
RefObject,
createRef,
linkEvent,
} from "inferno";
import { I18NextService } from "../../services";
import { Component, RefObject, createRef, linkEvent } from "inferno";
import { I18NextService } from "../../../services";
import type { Modal } from "bootstrap";
import { Spinner } from "./icon";
import { LoadingEllipses } from "./loading-ellipses";
import { modalMixin } from "../mixins/modal-mixin";
import { Spinner } from "../icon";
import { LoadingEllipses } from "../loading-ellipses";
import { modalMixin } from "../../mixins/modal-mixin";
import { MouseEventHandler } from "inferno";
interface ConfirmationModalProps {
children?: InfernoNode;
onYes: () => Promise<void>;
onNo: () => void;
onNo: MouseEventHandler<HTMLButtonElement>;
message: string;
loadingMessage: string;
show: boolean;

View file

@ -0,0 +1,386 @@
import {
Component,
FormEventHandler,
MouseEventHandler,
RefObject,
createRef,
linkEvent,
} from "inferno";
import type { Modal } from "bootstrap";
import { modalMixin } from "../../mixins/modal-mixin";
import { I18NextService } from "../../../services/I18NextService";
import {
CreateOAuthProvider,
EditOAuthProvider,
OAuthProvider,
} from "lemmy-js-client";
import { ProviderToEdit } from "@utils/types/oauth";
export type CreateOrEditOAuthProviderModalData =
| { type: "add"; provider?: ProviderToEdit }
| { type: "edit" | "add"; provider: OAuthProvider };
interface CreateOrEditOAuthProviderModalProps {
onClose: MouseEventHandler<HTMLButtonElement>;
show: boolean;
data: CreateOrEditOAuthProviderModalData;
onSubmit: (
provider: CreateOAuthProvider | EditOAuthProvider,
) => Promise<void>;
}
interface CreateOrEditOAuthProviderModalState {
changed: boolean;
provider: Partial<CreateOAuthProvider>;
loading: boolean;
}
interface ProviderFieldProps {
id: string;
i18nKey: string;
onInput: FormEventHandler<HTMLInputElement>;
}
interface ProviderTextFieldProps extends ProviderFieldProps {
disabled?: boolean;
placeholder?: string;
type?: "text" | "url" | "password";
value?: string;
required?: boolean;
}
type ProviderBooleanProperties =
| "enabled"
| "account_linking_enabled"
| "auto_verify_email";
interface ProviderCheckboxFieldProps extends ProviderFieldProps {
checked?: boolean;
}
const FORM_ID = "create-or-edit-oauth-provider-form-id";
function handleTextPropertyChange(
{
modal,
property,
}: {
modal: CreateOrEditOAuthProviderModal;
property: Exclude<keyof CreateOAuthProvider, ProviderBooleanProperties>;
},
event: any,
) {
modal.setState(prevState => ({
changed: true,
provider: {
...prevState.provider,
[property]: event.target.value,
},
}));
}
function handleBooleanPropertyChange({
modal,
property,
}: {
modal: CreateOrEditOAuthProviderModal;
property: Extract<keyof ProviderToEdit, ProviderBooleanProperties>;
}) {
modal.setState(prevState => ({
changed: true,
provider: {
...prevState.provider,
[property]: !prevState.provider[property],
},
}));
}
function ProviderTextField({
id,
i18nKey,
type = "text",
value,
onInput,
required = true,
disabled,
placeholder,
}: ProviderTextFieldProps) {
return (
<div className="col">
<label className="form-label" htmlFor={id}>
{I18NextService.i18n.t(i18nKey)}
</label>
<input
type={type}
id={id}
className="form-control"
value={value}
onInput={onInput}
required={required}
disabled={disabled}
placeholder={placeholder}
/>
</div>
);
}
function ProviderCheckboxField({
i18nKey,
id,
onInput,
checked,
}: ProviderCheckboxFieldProps) {
return (
<div className="form-check form-check-inline m-2">
<input
id={id}
type="checkbox"
className="form-check-input"
checked={checked}
onInput={onInput}
/>
<label htmlFor={id} className="form-check-label">
{I18NextService.i18n.t(i18nKey)}
</label>
</div>
);
}
@modalMixin
export default class CreateOrEditOAuthProviderModal extends Component<
CreateOrEditOAuthProviderModalProps,
CreateOrEditOAuthProviderModalState
> {
readonly modalDivRef: RefObject<HTMLDivElement>;
modal?: Modal;
state: CreateOrEditOAuthProviderModalState = {
changed: false,
provider: {},
loading: false,
};
constructor(props: CreateOrEditOAuthProviderModalProps, context: any) {
super(props, context);
this.modalDivRef = createRef();
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidUpdate(prevProps: Readonly<CreateOrEditOAuthProviderModalProps>) {
if (this.props.show && this.props.show !== prevProps.show) {
this.setState({ provider: this.props.data.provider ?? {} });
}
}
render(
{ onClose, data }: CreateOrEditOAuthProviderModalProps,
{ provider, changed, loading }: CreateOrEditOAuthProviderModalState,
) {
return (
<div
className="modal fade"
id="create-or-edit-oauth-modal"
tabIndex={-1}
aria-hidden
aria-labelledby="#create-or-edit-oauth-modal-title"
data-bs-backdrop="static"
data-bs-keyboard="false"
ref={this.modalDivRef}
>
<div className="modal-dialog modal-fullscreen-sm-down">
<div className="modal-content">
<div className="modal-header">
<h1
className="modal-title h4"
id="create-or-edit-oauth-modal-title"
>
{data.type === "edit"
? `Edit ${data.provider.display_name}`
: "Add OAuth Provider"}
</h1>
<button
type="button"
className="btn-close"
aria-label={I18NextService.i18n.t("cancel")}
onClick={onClose}
/>
</div>
<div className="modal-body p-2-!important">
<form
id={FORM_ID}
className="container"
onSubmit={this.handleSubmit}
>
<div className="row row-cols-1 mb-3 gy-2">
<ProviderTextField
id="display-name"
i18nKey="oauth_display_name"
value={provider?.display_name}
onInput={linkEvent(
{ modal: this, property: "display_name" },
handleTextPropertyChange,
)}
/>
<ProviderTextField
id="issuer"
i18nKey="oauth_issuer"
value={provider?.issuer}
onInput={linkEvent(
{ modal: this, property: "issuer" },
handleTextPropertyChange,
)}
type="url"
disabled={data.type === "edit"}
/>
<ProviderTextField
id="authorization-endpoint"
i18nKey="oauth_authorization_endpoint"
value={provider?.authorization_endpoint}
onInput={linkEvent(
{ modal: this, property: "authorization_endpoint" },
handleTextPropertyChange,
)}
type="url"
/>
<ProviderTextField
id="token-endpoint"
i18nKey="oauth_token_endpoint"
value={provider?.token_endpoint}
onInput={linkEvent(
{ modal: this, property: "token_endpoint" },
handleTextPropertyChange,
)}
type="url"
/>
<ProviderTextField
id="userinfo-endpoint"
i18nKey="oauth_userinfo_endpoint"
value={provider?.userinfo_endpoint}
onInput={linkEvent(
{ modal: this, property: "userinfo_endpoint" },
handleTextPropertyChange,
)}
type="url"
/>
<ProviderTextField
id="id-claim"
i18nKey="oauth_id_claim"
value={provider?.id_claim}
onInput={linkEvent(
{ modal: this, property: "id_claim" },
handleTextPropertyChange,
)}
/>
<ProviderTextField
id="client-id"
i18nKey="oauth_client_id"
value={provider?.client_id}
disabled={data.type === "edit"}
onInput={linkEvent(
{ modal: this, property: "client_id" },
handleTextPropertyChange,
)}
/>
<ProviderTextField
id="client-secret"
i18nKey="oauth_client_secret"
onInput={linkEvent(
{ modal: this, property: "client_secret" },
handleTextPropertyChange,
)}
type="password"
placeholder={
data.type === "edit"
? I18NextService.i18n.t(
"cannot_view_secret_after_saving",
)
: undefined
}
required={data.type === "add"}
value={provider.client_secret}
/>
<ProviderTextField
id="scopes"
i18nKey="oauth_scopes"
value={provider?.scopes}
onInput={linkEvent(
{ modal: this, property: "scopes" },
handleTextPropertyChange,
)}
/>
</div>
<div className="row">
<div className="col">
<ProviderCheckboxField
id="auto-verfiy-email"
i18nKey="oauth_auto_verify_email"
checked={provider?.auto_verify_email}
onInput={linkEvent(
{
modal: this,
property: "auto_verify_email",
},
handleBooleanPropertyChange,
)}
/>
<ProviderCheckboxField
id="account-linking-enabled"
i18nKey="oauth_account_linking_enabled"
checked={provider?.account_linking_enabled}
onInput={linkEvent(
{
modal: this,
property: "account_linking_enabled",
},
handleBooleanPropertyChange,
)}
/>
<ProviderCheckboxField
id="oauth-enabled"
i18nKey="oauth_enabled"
checked={provider?.enabled ?? true}
onInput={linkEvent(
{
modal: this,
property: "enabled",
},
handleBooleanPropertyChange,
)}
/>
</div>
</div>
</form>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-danger"
onClick={onClose}
>
{I18NextService.i18n.t("cancel")}
</button>
<button
type="submit"
form={FORM_ID}
className="btn btn-success"
disabled={!changed || loading}
>
{I18NextService.i18n.t(data.type === "edit" ? "edit" : "add")}
</button>
</div>
</div>
</div>
</div>
);
}
async handleSubmit(event) {
event.preventDefault();
this.setState({ loading: true });
await this.props.onSubmit(this.state.provider as CreateOAuthProvider);
this.setState({ loading: false, changed: false, provider: {} });
}
}

View file

@ -0,0 +1,77 @@
import {
Component,
InfernoNode,
MouseEventHandler,
RefObject,
createRef,
} from "inferno";
import type { Modal } from "bootstrap";
import { Spinner } from "../icon";
import { LoadingEllipses } from "../loading-ellipses";
import { modalMixin } from "../../mixins/modal-mixin";
interface DisplayModalProps {
children: InfernoNode;
loadingMessage?: string;
title: string;
onClose: MouseEventHandler<HTMLButtonElement>;
show: boolean;
loading?: boolean;
}
@modalMixin
export default class DisplayModal extends Component<DisplayModalProps, any> {
readonly modalDivRef: RefObject<HTMLDivElement>;
modal?: Modal;
constructor(props: DisplayModalProps, context: any) {
super(props, context);
this.modalDivRef = createRef();
}
render() {
const { children, loadingMessage, title, onClose, loading } = this.props;
return (
<div
className="modal fade"
id="display-modal"
tabIndex={-1}
aria-hidden
aria-labelledby="#displayModalTitle"
data-bs-backdrop="static"
ref={this.modalDivRef}
>
<div className="modal-dialog modal-fullscreen-sm-down">
<div className="modal-content">
<header className="modal-header">
<h3 className="modal-title" id="displayModalTitle">
{title}
</h3>
<button
type="button"
className="btn-close"
aria-label="Close"
onClick={onClose}
/>
</header>
<div className="modal-body">
{loading ? (
<div class="text-center align-middle text-body">
<Spinner large />
<div>
{loadingMessage}
<LoadingEllipses />
</div>
</div>
) : (
children
)}
</div>
</div>
</div>
</div>
);
}
}

View file

@ -5,18 +5,18 @@ import {
createRef,
linkEvent,
} from "inferno";
import { I18NextService } from "../../services/I18NextService";
import { PurgeWarning, Spinner } from "./icon";
import { I18NextService } from "../../../services/I18NextService";
import { PurgeWarning, Spinner } from "../icon";
import { getApubName, randomStr } from "@utils/helpers";
import type { Modal } from "bootstrap";
import classNames from "classnames";
import { Community, Person } from "lemmy-js-client";
import { LoadingEllipses } from "./loading-ellipses";
import { modalMixin } from "../mixins/modal-mixin";
import { LoadingEllipses } from "../loading-ellipses";
import { modalMixin } from "../../mixins/modal-mixin";
export interface BanUpdateForm {
reason?: string;
shouldRemove?: boolean;
shouldRemoveOrRestoreData?: boolean;
daysUntilExpires?: number;
}
@ -31,7 +31,7 @@ interface ModActionFormModalPropsCommunityBan {
modActionType: "community-ban";
onSubmit: (form: BanUpdateForm) => Promise<void>;
creator: Person;
community: Community;
community?: Community;
isBanned: boolean;
}
@ -69,7 +69,7 @@ interface ModActionFormFormState {
loading: boolean;
reason: string;
daysUntilExpire?: number;
shouldRemoveData?: boolean;
shouldRemoveOrRestoreData?: boolean;
shouldPermaBan?: boolean;
}
@ -84,7 +84,7 @@ function handleExpiryChange(i: ModActionFormModal, event: any) {
function handleToggleRemove(i: ModActionFormModal) {
i.setState(prev => ({
...prev,
shouldRemoveData: !prev.shouldRemoveData,
shouldRemoveOrRestoreData: !prev.shouldRemoveOrRestoreData,
}));
}
@ -104,7 +104,7 @@ async function handleSubmit(i: ModActionFormModal, event: any) {
await i.props.onSubmit({
reason: i.state.reason,
daysUntilExpires: i.state.daysUntilExpire!,
shouldRemove: i.state.shouldRemoveData!,
shouldRemoveOrRestoreData: i.state.shouldRemoveOrRestoreData!,
} as BanUpdateForm & string); // Need to & string to handle type weirdness
} else {
await i.props.onSubmit(i.state.reason);
@ -135,7 +135,7 @@ export default class ModActionFormModal extends Component<
this.reasonRef = createRef();
if (this.isBanModal) {
this.state.shouldRemoveData = false;
this.state.shouldRemoveOrRestoreData = false;
}
}
@ -144,7 +144,7 @@ export default class ModActionFormModal extends Component<
loading,
reason,
daysUntilExpire,
shouldRemoveData,
shouldRemoveOrRestoreData,
shouldPermaBan,
} = this.state;
const reasonId = `mod-form-reason-${randomStr()}`;
@ -249,7 +249,7 @@ export default class ModActionFormModal extends Component<
<input
className="form-check-input user-select-none"
type="checkbox"
checked={shouldRemoveData}
checked={shouldRemoveOrRestoreData}
onChange={linkEvent(this, handleToggleRemove)}
/>
{I18NextService.i18n.t("remove_content")}
@ -328,7 +328,12 @@ export default class ModActionFormModal extends Component<
: "ban_from_community_with_name",
{
user: getApubName(this.props.creator),
community: getApubName(this.props.community),
community: getApubName(
this.props.community ?? {
actor_id: "",
name: "",
},
),
},
);
}

View file

@ -6,10 +6,10 @@ import {
createRef,
linkEvent,
} from "inferno";
import { I18NextService } from "../../services";
import { toast } from "../../toast";
import { I18NextService } from "../../../services";
import { toast } from "../../../toast";
import type { Modal } from "bootstrap";
import { modalMixin } from "../mixins/modal-mixin";
import { modalMixin } from "../../mixins/modal-mixin";
interface TotpModalProps {
children?: InfernoNode;
@ -201,7 +201,7 @@ export default class TotpModal extends Component<
this.inputRef.current?.focus();
if (this.props.type === "generate") {
const { getSVG } = await import("@shortcm/qr-image/lib/svg");
const { getSVG } = await import("qreator/lib/svg");
this.setState({
qrCode: URL.createObjectURL(

View file

@ -5,10 +5,10 @@ import {
createRef,
linkEvent,
} from "inferno";
import { I18NextService } from "../../services";
import { I18NextService } from "../../../services";
import type { Modal } from "bootstrap";
import { Icon, Spinner } from "./icon";
import { Paginator } from "../common/paginator";
import { Icon, Spinner } from "../icon";
import { Paginator } from "../paginator";
import {
ListCommentLikesResponse,
ListPostLikesResponse,
@ -19,11 +19,12 @@ import {
HttpService,
LOADING_REQUEST,
RequestState,
} from "../../services/HttpService";
import { fetchLimit } from "../../config";
import { PersonListing } from "../person/person-listing";
import { modalMixin } from "../mixins/modal-mixin";
import { UserBadges } from "./user-badges";
} from "../../../services/HttpService";
import { fetchLimit } from "../../../config";
import { PersonListing } from "../../person/person-listing";
import { modalMixin } from "../../mixins/modal-mixin";
import { UserBadges } from "../user-badges";
import { isBrowser } from "@utils/browser";
interface ViewVotesModalProps {
children?: InfernoNode;
@ -96,8 +97,8 @@ export default class ViewVotesModal extends Component<
this.handlePageChange = this.handlePageChange.bind(this);
}
async componentDidMount() {
if (this.props.show) {
async componentWillMount() {
if (this.props.show && isBrowser()) {
await this.refetch();
}
}

View file

@ -5,6 +5,7 @@ interface PaginatorProps {
page: number;
onChange(val: number): any;
nextDisabled: boolean;
disabled?: boolean;
}
export class Paginator extends Component<PaginatorProps, any> {
@ -18,6 +19,7 @@ export class Paginator extends Component<PaginatorProps, any> {
<button
className="btn btn-secondary me-2"
onClick={linkEvent(this, this.handlePrev)}
disabled={this.props.disabled}
>
{I18NextService.i18n.t("prev")}
</button>
@ -26,6 +28,7 @@ export class Paginator extends Component<PaginatorProps, any> {
<button
className="btn btn-secondary"
onClick={linkEvent(this, this.handleNext)}
disabled={this.props.disabled}
>
{I18NextService.i18n.t("next")}
</button>

View file

@ -16,6 +16,7 @@ interface PasswordInputProps {
label?: string | null;
showForgotLink?: boolean;
isNew?: boolean;
required?: boolean;
}
interface PasswordInputState {
@ -77,6 +78,7 @@ class PasswordInput extends Component<PasswordInputProps, PasswordInputState> {
label,
showForgotLink,
isNew,
required,
},
state: { show },
} = this;
@ -98,9 +100,11 @@ class PasswordInput extends Component<PasswordInputProps, PasswordInputState> {
autoComplete={isNew ? "new-password" : "current-password"}
onInput={onInput}
value={value}
required
pattern=".{10,60}"
required={required !== false}
pattern=".+"
title={I18NextService.i18n.t("invalid_password")}
minLength={isNew ? 10 : undefined}
maxLength={isNew ? 60 : undefined}
/>
<button
className="btn btn-outline-dark"

View file

@ -39,7 +39,7 @@ function handleSearch(i: SearchableSelect, e: ChangeEvent<HTMLInputElement>) {
}
function focusSearch(i: SearchableSelect) {
if (i.toggleButtonRef.current?.ariaExpanded !== "true") {
if (i.toggleButtonRef.current?.ariaExpanded === "true") {
i.searchInputRef.current?.focus();
if (i.props.onSearch) {

View file

@ -1,19 +1,19 @@
import { randomStr } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { SortType } from "lemmy-js-client";
import { PostSortType } from "lemmy-js-client";
import { relTags, sortingHelpUrl } from "../../config";
import { I18NextService } from "../../services";
import { Icon } from "./icon";
interface SortSelectProps {
sort: SortType;
onChange(val: SortType): void;
sort: PostSortType;
onChange(val: PostSortType): void;
hideHot?: boolean;
hideMostComments?: boolean;
}
interface SortSelectState {
sort: SortType;
sort: PostSortType;
}
export class SortSelect extends Component<SortSelectProps, SortSelectState> {
@ -47,55 +47,53 @@ export class SortSelect extends Component<SortSelectProps, SortSelectState> {
{I18NextService.i18n.t("sort_type")}
</option>
{!this.props.hideHot && [
<option key={"Hot"} value={"Hot"}>
<option key="Hot" value="Hot">
{I18NextService.i18n.t("hot")}
</option>,
<option key={"Active"} value={"Active"}>
<option key="Active" value="Active">
{I18NextService.i18n.t("active")}
</option>,
<option key={"Scaled"} value={"Scaled"}>
<option key="Scaled" value="Scaled">
{I18NextService.i18n.t("scaled")}
</option>,
]}
<option value={"Controversial"}>
<option value="Controversial">
{I18NextService.i18n.t("controversial")}
</option>
<option value={"New"}>{I18NextService.i18n.t("new")}</option>
<option value={"Old"}>{I18NextService.i18n.t("old")}</option>
<option value="New">{I18NextService.i18n.t("new")}</option>
<option value="Old">{I18NextService.i18n.t("old")}</option>
{!this.props.hideMostComments && [
<option key={"MostComments"} value={"MostComments"}>
<option key="MostComments" value="MostComments">
{I18NextService.i18n.t("most_comments")}
</option>,
<option key={"NewComments"} value={"NewComments"}>
<option key="NewComments" value="NewComments">
{I18NextService.i18n.t("new_comments")}
</option>,
]}
<option disabled aria-hidden="true">
</option>
<option value={"TopHour"}>{I18NextService.i18n.t("top_hour")}</option>
<option value={"TopSixHour"}>
<option value="TopHour">{I18NextService.i18n.t("top_hour")}</option>
<option value="TopSixHour">
{I18NextService.i18n.t("top_six_hours")}
</option>
<option value={"TopTwelveHour"}>
<option value="TopTwelveHour">
{I18NextService.i18n.t("top_twelve_hours")}
</option>
<option value={"TopDay"}>{I18NextService.i18n.t("top_day")}</option>
<option value={"TopWeek"}>{I18NextService.i18n.t("top_week")}</option>
<option value={"TopMonth"}>
{I18NextService.i18n.t("top_month")}
</option>
<option value={"TopThreeMonths"}>
<option value="TopDay">{I18NextService.i18n.t("top_day")}</option>
<option value="TopWeek">{I18NextService.i18n.t("top_week")}</option>
<option value="TopMonth">{I18NextService.i18n.t("top_month")}</option>
<option value="TopThreeMonths">
{I18NextService.i18n.t("top_three_months")}
</option>
<option value={"TopSixMonths"}>
<option value="TopSixMonths">
{I18NextService.i18n.t("top_six_months")}
</option>
<option value={"TopNineMonths"}>
<option value="TopNineMonths">
{I18NextService.i18n.t("top_nine_months")}
</option>
<option value={"TopYear"}>{I18NextService.i18n.t("top_year")}</option>
<option value={"TopAll"}>{I18NextService.i18n.t("top_all")}</option>
<option value="TopYear">{I18NextService.i18n.t("top_year")}</option>
<option value="TopAll">{I18NextService.i18n.t("top_all")}</option>
</select>
<a
className="sort-select-icon text-muted"

View file

@ -1,12 +1,13 @@
import { getQueryString, validInstanceTLD } from "@utils/helpers";
import classNames from "classnames";
import { NoOptionI18nKeys } from "i18next";
import { Component, MouseEventHandler, linkEvent } from "inferno";
import { Component, MouseEventHandler, createRef, linkEvent } from "inferno";
import { CommunityView } from "lemmy-js-client";
import { I18NextService, UserService } from "../../services";
import { VERSION } from "../../version";
import { Icon, Spinner } from "./icon";
import { toast } from "../../toast";
import { modalMixin } from "../mixins/modal-mixin";
interface SubscribeButtonProps {
communityView: CommunityView;
@ -93,6 +94,7 @@ export function SubscribeButton({
interface RemoteFetchModalProps {
communityActorId: string;
show?: boolean;
}
interface RemoteFetchModalState {
@ -103,10 +105,6 @@ function handleInput(i: RemoteFetchModal, event: any) {
i.setState({ instanceText: event.target.value });
}
function focusInput() {
document.getElementById("remoteFetchInstance")?.focus();
}
function submitRemoteFollow(
{ state: { instanceText }, props: { communityActorId } }: RemoteFetchModal,
event: Event,
@ -139,6 +137,7 @@ function submitRemoteFollow(
)}`;
}
@modalMixin
class RemoteFetchModal extends Component<
RemoteFetchModalProps,
RemoteFetchModalState
@ -147,20 +146,15 @@ class RemoteFetchModal extends Component<
instanceText: "",
};
modalDivRef = createRef<HTMLDivElement>();
inputRef = createRef<HTMLInputElement>();
constructor(props: any, context: any) {
super(props, context);
}
componentDidMount() {
document
.getElementById("remoteFetchModal")
?.addEventListener("shown.bs.modal", focusInput);
}
componentWillUnmount(): void {
document
.getElementById("remoteFetchModal")
?.removeEventListener("shown.bs.modal", focusInput);
handleShow() {
this.inputRef.current?.focus();
}
render() {
@ -171,6 +165,7 @@ class RemoteFetchModal extends Component<
tabIndex={-1}
aria-hidden
aria-labelledby="#remoteFetchModalTitle"
ref={this.modalDivRef}
>
<div className="modal-dialog modal-dialog-centered modal-fullscreen-sm-down">
<div className="modal-content">
@ -203,6 +198,7 @@ class RemoteFetchModal extends Component<
required
enterKeyHint="go"
inputMode="url"
ref={this.inputRef}
/>
</form>
<footer className="modal-footer">

View file

@ -16,7 +16,7 @@ import {
ListCommunities,
ListCommunitiesResponse,
ListingType,
SortType,
PostSortType,
} from "lemmy-js-client";
import { InitialFetchRequest } from "../../interfaces";
import { FirstLoadService, I18NextService } from "../../services";
@ -40,6 +40,7 @@ import { getHttpBaseInternal } from "../../utils/env";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { scrollMixin } from "../mixins/scroll-mixin";
import { isBrowser } from "@utils/browser";
type CommunitiesData = RouteDataResponse<{
listCommunitiesResponse: ListCommunitiesResponse;
@ -54,7 +55,7 @@ interface CommunitiesState {
interface CommunitiesProps {
listingType: ListingType;
sort: SortType;
sort: PostSortType;
page: number;
}
@ -62,8 +63,8 @@ function getListingTypeFromQuery(listingType?: string): ListingType {
return listingType ? (listingType as ListingType) : "Local";
}
function getSortTypeFromQuery(type?: string): SortType {
return type ? (type as SortType) : "TopMonth";
function getSortTypeFromQuery(type?: string): PostSortType {
return type ? (type as PostSortType) : "TopMonth";
}
export function getCommunitiesQueryParams(source?: string): CommunitiesProps {
@ -121,19 +122,23 @@ export class Communities extends Component<
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
await this.refetch();
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.refetch(this.props);
}
}
componentWillReceiveProps(nextProps: CommunitiesRouteProps) {
this.refetch(nextProps);
}
get documentTitle(): string {
return `${I18NextService.i18n.t("communities")} - ${
this.state.siteRes.site_view.site.name
}`;
}
renderListings() {
renderListingsTable() {
switch (this.state.listCommunitiesResponse.state) {
case "loading":
return (
@ -142,120 +147,114 @@ export class Communities extends Component<
</h5>
);
case "success": {
const { listingType, sort, page } = this.props;
return (
<div>
<h1 className="h4 mb-4">
{I18NextService.i18n.t("list_of_communities")}
</h1>
<div className="row g-3 align-items-center mb-2">
<div className="col-auto">
<ListingTypeSelect
type_={listingType}
showLocal={showLocal(this.isoData)}
showSubscribed
onChange={this.handleListingTypeChange}
/>
</div>
<div className="col-auto me-auto">
<SortSelect sort={sort} onChange={this.handleSortChange} />
</div>
<div className="col-auto">{this.searchForm()}</div>
</div>
<div className="table-responsive">
<table
id="community_table"
className="table table-sm table-hover"
>
<thead className="pointer">
<tr>
<th>{I18NextService.i18n.t("name")}</th>
<th className="text-right">
{I18NextService.i18n.t("subscribers")}
</th>
<th className="text-right">
{I18NextService.i18n.t("users")} /{" "}
{I18NextService.i18n.t("month")}
</th>
<th className="text-right d-none d-lg-table-cell">
{I18NextService.i18n.t("posts")}
</th>
<th className="text-right d-none d-lg-table-cell">
{I18NextService.i18n.t("comments")}
</th>
<th></th>
</tr>
</thead>
<tbody>
{this.state.listCommunitiesResponse.data.communities.map(
cv => (
<tr key={cv.community.id}>
<td>
<CommunityLink community={cv.community} />
</td>
<td className="text-right">
{numToSI(cv.counts.subscribers)}
</td>
<td className="text-right">
{numToSI(cv.counts.users_active_month)}
</td>
<td className="text-right d-none d-lg-table-cell">
{numToSI(cv.counts.posts)}
</td>
<td className="text-right d-none d-lg-table-cell">
{numToSI(cv.counts.comments)}
</td>
<td className="text-right">
<SubscribeButton
communityView={cv}
onFollow={linkEvent(
{
i: this,
communityId: cv.community.id,
follow: true,
},
this.handleFollow,
)}
onUnFollow={linkEvent(
{
i: this,
communityId: cv.community.id,
follow: false,
},
this.handleFollow,
)}
isLink
/>
</td>
</tr>
),
)}
</tbody>
</table>
</div>
<Paginator
page={page}
onChange={this.handlePageChange}
nextDisabled={
communityLimit >
this.state.listCommunitiesResponse.data.communities.length
}
/>
</div>
<table id="community_table" className="table table-sm table-hover">
<thead className="pointer">
<tr>
<th>{I18NextService.i18n.t("name")}</th>
<th className="text-right">
{I18NextService.i18n.t("subscribers")}
</th>
<th className="text-right">
{I18NextService.i18n.t("users")} /{" "}
{I18NextService.i18n.t("month")}
</th>
<th className="text-right d-none d-lg-table-cell">
{I18NextService.i18n.t("posts")}
</th>
<th className="text-right d-none d-lg-table-cell">
{I18NextService.i18n.t("comments")}
</th>
<th></th>
</tr>
</thead>
<tbody>
{this.state.listCommunitiesResponse.data.communities.map(cv => (
<tr key={cv.community.id}>
<td>
<CommunityLink community={cv.community} />
</td>
<td className="text-right">
{numToSI(cv.counts.subscribers)}
</td>
<td className="text-right">
{numToSI(cv.counts.users_active_month)}
</td>
<td className="text-right d-none d-lg-table-cell">
{numToSI(cv.counts.posts)}
</td>
<td className="text-right d-none d-lg-table-cell">
{numToSI(cv.counts.comments)}
</td>
<td className="text-right">
<SubscribeButton
communityView={cv}
onFollow={linkEvent(
{
i: this,
communityId: cv.community.id,
follow: true,
},
this.handleFollow,
)}
onUnFollow={linkEvent(
{
i: this,
communityId: cv.community.id,
follow: false,
},
this.handleFollow,
)}
isLink
/>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
}
render() {
const { listingType, sort, page } = this.props;
return (
<div className="communities container-lg">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
{this.renderListings()}
<div>
<h1 className="h4 mb-4">
{I18NextService.i18n.t("list_of_communities")}
</h1>
<div className="row g-3 align-items-center mb-2">
<div className="col-auto">
<ListingTypeSelect
type_={listingType}
showLocal={showLocal(this.isoData)}
showSubscribed
onChange={this.handleListingTypeChange}
/>
</div>
<div className="col-auto me-auto">
<SortSelect sort={sort} onChange={this.handleSortChange} />
</div>
<div className="col-auto">{this.searchForm()}</div>
</div>
<div className="table-responsive">{this.renderListingsTable()}</div>
<Paginator
page={page}
onChange={this.handlePageChange}
nextDisabled={
this.state.listCommunitiesResponse.state !== "success" ||
communityLimit >
this.state.listCommunitiesResponse.data.communities.length
}
/>
</div>
</div>
);
}
@ -287,29 +286,23 @@ export class Communities extends Component<
);
}
async updateUrl({ listingType, sort, page }: Partial<CommunitiesProps>) {
const {
listingType: urlListingType,
sort: urlSort,
page: urlPage,
} = this.props;
async updateUrl(props: Partial<CommunitiesProps>) {
const { listingType, sort, page } = { ...this.props, ...props };
const queryParams: QueryParams<CommunitiesProps> = {
listingType: listingType ?? urlListingType,
sort: sort ?? urlSort,
page: (page ?? urlPage)?.toString(),
listingType: listingType,
sort: sort,
page: page?.toString(),
};
this.props.history.push(`/communities${getQueryString(queryParams)}`);
await this.refetch();
}
handlePageChange(page: number) {
this.updateUrl({ page });
}
handleSortChange(val: SortType) {
handleSortChange(val: PostSortType) {
this.updateUrl({ sort: val, page: 1 });
}
@ -368,19 +361,19 @@ export class Communities extends Component<
data.i.findAndUpdateCommunity(res);
}
async refetch() {
fetchToken?: symbol;
async refetch({ listingType, sort, page }: CommunitiesProps) {
const token = (this.fetchToken = Symbol());
this.setState({ listCommunitiesResponse: LOADING_REQUEST });
const { listingType, sort, page } = this.props;
this.setState({
listCommunitiesResponse: await HttpService.client.listCommunities({
type_: listingType,
sort: sort,
limit: communityLimit,
page,
}),
const listCommunitiesResponse = await HttpService.client.listCommunities({
type_: listingType,
sort: sort,
limit: communityLimit,
page,
});
if (token === this.fetchToken) {
this.setState({ listCommunitiesResponse });
}
}
findAndUpdateCommunity(res: RequestState<CommunityResponse>) {

View file

@ -14,6 +14,7 @@ import { ImageUploadForm } from "../common/image-upload-form";
import { LanguageSelect } from "../common/language-select";
import { MarkdownTextArea } from "../common/markdown-textarea";
import { tippyMixin } from "../mixins/tippy-mixin";
import { validActorRegexPattern } from "../../config";
interface CommunityFormProps {
community_view?: CommunityView; // If a community is given, that means this is an edit
@ -129,7 +130,7 @@ export class CommunityForm extends Component<
onInput={linkEvent(this, this.handleCommunityNameChange)}
required
minLength={3}
pattern="[a-z0-9_]+"
pattern={validActorRegexPattern}
title={I18NextService.i18n.t("community_reqs")}
/>
</div>

View file

@ -28,7 +28,7 @@ export class CommunityLink extends Component<CommunityLinkProps, any> {
const title = useApubName
? community.name
: community.title ?? community.name;
: (community.title ?? community.name);
if (local) {
link = `/c/${community.name}`;

View file

@ -1,12 +1,12 @@
import {
commentsToFlatNodes,
commentToPostSortType,
communityRSSUrl,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
getDataTypeString,
postToCommentSortType,
setIsoData,
@ -19,11 +19,18 @@ import {
getQueryParams,
getQueryString,
resourcesSettled,
bareRoutePush,
} from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import type { QueryParams, StringBoolean } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { Component, RefObject, createRef, linkEvent } from "inferno";
import {
Component,
InfernoNode,
RefObject,
createRef,
linkEvent,
} from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
AddAdmin,
@ -35,7 +42,6 @@ import {
BanPersonResponse,
BlockCommunity,
BlockPerson,
CommentId,
CommentReplyResponse,
CommentResponse,
CommunityResponse,
@ -76,9 +82,10 @@ import {
RemovePost,
SaveComment,
SavePost,
SortType,
PostSortType,
SuccessResponse,
TransferCommunity,
CommentSortType,
} from "lemmy-js-client";
import { fetchLimit, relTags } from "../../config";
import {
@ -100,7 +107,7 @@ import { CommentNodes } from "../comment/comment-nodes";
import { BannerIconHeader } from "../common/banner-icon-header";
import { DataTypeSelect } from "../common/data-type-select";
import { HtmlTags } from "../common/html-tags";
import { Icon, Spinner } from "../common/icon";
import { Icon } from "../common/icon";
import { SortSelect } from "../common/sort-select";
import { SiteSidebar } from "../home/site-sidebar";
import { PostListings } from "../post/post-listings";
@ -114,6 +121,9 @@ import {
import { Sidebar } from "./sidebar";
import { IRoutePropsWithFetch } from "../../routes";
import PostHiddenSelect from "../common/post-hidden-select";
import { isBrowser } from "@utils/browser";
import { LoadingEllipses } from "../common/loading-ellipses";
import { CommentSortSelect } from "../common/comment-sort-select";
type CommunityData = RouteDataResponse<{
communityRes: GetCommunityResponse;
@ -127,18 +137,17 @@ interface State {
commentsRes: RequestState<GetCommentsResponse>;
siteRes: GetSiteResponse;
showSidebarMobile: boolean;
finished: Map<CommentId, boolean | undefined>;
isIsomorphic: boolean;
}
interface CommunityProps {
dataType: DataType;
sort: SortType;
sort: PostSortType;
pageCursor?: PaginationCursor;
showHidden?: StringBoolean;
}
type Fallbacks = { sort: SortType };
type Fallbacks = { sort: PostSortType };
export function getCommunityQueryParams(
source: string | undefined,
@ -156,7 +165,8 @@ export function getCommunityQueryParams(
},
source,
{
sort: local_user?.default_sort_type ?? local_site.default_sort_type,
sort:
local_user?.default_post_sort_type ?? local_site.default_post_sort_type,
},
);
}
@ -165,12 +175,11 @@ function getDataTypeFromQuery(type?: string): DataType {
return type ? DataType[type] : DataType.Post;
}
function getSortTypeFromQuery(type?: string): SortType {
const mySortType =
UserService.Instance.myUserInfo?.local_user_view.local_user
.default_sort_type;
return type ? (type as SortType) : mySortType ?? "Active";
function getSortTypeFromQuery(
type: string | undefined,
fallback: PostSortType,
): PostSortType {
return type ? (type as PostSortType) : fallback;
}
type CommunityPathProps = { name: string };
@ -192,7 +201,6 @@ export class Community extends Component<CommunityRouteProps, State> {
commentsRes: EMPTY_REQUEST,
siteRes: this.isoData.site_res,
showSidebarMobile: false,
finished: new Map(),
isIsomorphic: false,
};
private readonly mainContentRef: RefObject<HTMLElement>;
@ -210,6 +218,7 @@ export class Community extends Component<CommunityRouteProps, State> {
super(props, context);
this.handleSortChange = this.handleSortChange.bind(this);
this.handleCommentSortChange = this.handleCommentSortChange.bind(this);
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
this.handlePageNext = this.handlePageNext.bind(this);
this.handlePagePrev = this.handlePagePrev.bind(this);
@ -265,21 +274,39 @@ export class Community extends Component<CommunityRouteProps, State> {
}
}
async fetchCommunity() {
fetchCommunityToken?: symbol;
async fetchCommunity(props: CommunityRouteProps) {
const token = (this.fetchCommunityToken = Symbol());
this.setState({ communityRes: LOADING_REQUEST });
this.setState({
communityRes: await HttpService.client.getCommunity({
name: this.props.match.params.name,
}),
const communityRes = await HttpService.client.getCommunity({
name: props.match.params.name,
});
if (token === this.fetchCommunityToken) {
this.setState({ communityRes });
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
await Promise.all([this.fetchCommunity(), this.fetchData()]);
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await Promise.all([
this.fetchCommunity(this.props),
this.fetchData(this.props),
]);
}
}
componentWillReceiveProps(
nextProps: CommunityRouteProps & { children?: InfernoNode },
) {
if (
bareRoutePush(this.props, nextProps) ||
this.props.match.params.name !== nextProps.match.params.name
) {
this.fetchCommunity(nextProps);
}
this.fetchData(nextProps);
}
static async fetchInitialData({
headers,
query: { dataType, pageCursor, sort, showHidden },
@ -356,73 +383,67 @@ export class Community extends Component<CommunityRouteProps, State> {
}
renderCommunity() {
switch (this.state.communityRes.state) {
case "loading":
return (
<h5>
<Spinner large />
</h5>
);
case "success": {
const res = this.state.communityRes.data;
const res =
this.state.communityRes.state === "success" &&
this.state.communityRes.data;
return (
<>
{res && (
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
canonicalPath={res.community_view.community.actor_id}
description={res.community_view.community.description}
image={res.community_view.community.icon}
/>
)}
return (
<>
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
canonicalPath={res.community_view.community.actor_id}
description={res.community_view.community.description}
image={res.community_view.community.icon}
{this.communityInfo()}
<div className="d-block d-md-none">
<button
className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)}
>
{I18NextService.i18n.t("sidebar")}{" "}
<Icon
icon={
this.state.showSidebarMobile ? `minus-square` : `plus-square`
}
classes="icon-inline"
/>
<div className="row">
<main
className="col-12 col-md-8 col-lg-9"
ref={this.mainContentRef}
>
{this.communityInfo(res)}
<div className="d-block d-md-none">
<button
className="btn btn-secondary d-inline-block mb-2 me-3"
onClick={linkEvent(this, this.handleShowSidebarMobile)}
>
{I18NextService.i18n.t("sidebar")}{" "}
<Icon
icon={
this.state.showSidebarMobile
? `minus-square`
: `plus-square`
}
classes="icon-inline"
/>
</button>
{this.state.showSidebarMobile && this.sidebar(res)}
</div>
{this.selects(res)}
{this.listings(res)}
<PaginatorCursor
nextPage={this.getNextPage}
onNext={this.handlePageNext}
/>
</main>
<aside className="d-none d-md-block col-md-4 col-lg-3">
{this.sidebar(res)}
</aside>
</div>
</>
);
}
}
</button>
{this.state.showSidebarMobile && this.sidebar()}
</div>
</>
);
}
render() {
return (
<div className="community container-lg">{this.renderCommunity()}</div>
<div className="community container-lg">
<div className="row">
<main className="col-12 col-md-8 col-lg-9" ref={this.mainContentRef}>
{this.renderCommunity()}
{this.selects()}
{this.listings()}
<PaginatorCursor
nextPage={this.getNextPage}
onNext={this.handlePageNext}
/>
</main>
<aside className="d-none d-md-block col-md-4 col-lg-3">
{this.sidebar()}
</aside>
</div>
</div>
);
}
sidebar(res: GetCommunityResponse) {
sidebar() {
if (this.state.communityRes.state !== "success") {
return undefined;
}
const res = this.state.communityRes.data;
const siteRes = this.isoData.site_res;
// For some reason, this returns an empty vec if it matches the site langs
const communityLangs =
@ -456,7 +477,7 @@ export class Community extends Component<CommunityRouteProps, State> {
);
}
listings(communityRes: GetCommunityResponse) {
listings() {
const { dataType } = this.props;
const siteRes = this.isoData.site_res;
@ -468,7 +489,6 @@ export class Community extends Component<CommunityRouteProps, State> {
return (
<PostListings
posts={this.state.postsRes.data.posts}
removeDuplicates
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
enableNsfw={enableNsfw(siteRes)}
@ -496,6 +516,9 @@ export class Community extends Component<CommunityRouteProps, State> {
);
}
} else {
if (this.state.communityRes.state !== "success") {
return;
}
switch (this.state.commentsRes.state) {
case "loading":
return <CommentsLoadingSkeleton />;
@ -504,12 +527,11 @@ export class Community extends Component<CommunityRouteProps, State> {
<CommentNodes
nodes={commentsToFlatNodes(this.state.commentsRes.data.comments)}
viewType={CommentViewType.Flat}
finished={this.state.finished}
isTopLevel
showContext
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
moderators={communityRes.moderators}
moderators={this.state.communityRes.data.moderators}
admins={siteRes.admins}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
@ -537,28 +559,40 @@ export class Community extends Component<CommunityRouteProps, State> {
}
}
communityInfo(res: GetCommunityResponse) {
const community = res.community_view.community;
communityInfo() {
const res =
(this.state.communityRes.state === "success" &&
this.state.communityRes.data) ||
undefined;
const community = res && res.community_view.community;
const urlCommunityName = this.props.match.params.name;
return (
community && (
<div className="mb-2">
<div className="mb-2">
{community && (
<BannerIconHeader banner={community.banner} icon={community.icon} />
<div>
<h1
className="h4 mb-0 overflow-wrap-anywhere d-inline"
data-tippy-content={
community.posting_restricted_to_mods
? I18NextService.i18n.t("community_locked")
: ""
}
>
{community.title}
</h1>
{community.posting_restricted_to_mods && (
<Icon icon="lock" inline classes="text-danger fs-4 ms-2" />
)}
<div>
<h1
className="h4 mb-0 overflow-wrap-anywhere d-inline"
data-tippy-content={
community?.posting_restricted_to_mods
? I18NextService.i18n.t("community_locked")
: ""
}
>
{community?.title ?? (
<>
{urlCommunityName}
<LoadingEllipses />
</>
)}
</div>
</h1>
{community?.posting_restricted_to_mods && (
<Icon icon="lock" inline classes="text-danger fs-4 ms-2" />
)}
</div>
{(community && (
<CommunityLink
community={community}
realLink
@ -566,12 +600,16 @@ export class Community extends Component<CommunityRouteProps, State> {
muted
hideAvatar
/>
</div>
)
)) ??
urlCommunityName}
</div>
);
}
selects(res: GetCommunityResponse) {
selects() {
const res =
this.state.communityRes.state === "success" &&
this.state.communityRes.data;
const { dataType, sort, showHidden } = this.props;
const communityRss = res
? communityRSSUrl(res.community_view.community.actor_id, sort)
@ -594,7 +632,14 @@ export class Community extends Component<CommunityRouteProps, State> {
</span>
)}
<span className="me-2">
<SortSelect sort={sort} onChange={this.handleSortChange} />
{this.props.dataType === DataType.Post ? (
<SortSelect sort={sort} onChange={this.handleSortChange} />
) : (
<CommentSortSelect
sort={postToCommentSortType(sort)}
onChange={this.handleCommentSortChange}
/>
)}
</span>
{communityRss && (
<>
@ -620,8 +665,18 @@ export class Community extends Component<CommunityRouteProps, State> {
this.updateUrl({ pageCursor: nextPage });
}
handleSortChange(sort: SortType) {
this.updateUrl({ sort, pageCursor: undefined });
handleSortChange(sort: PostSortType) {
this.updateUrl({
sort: sort,
pageCursor: undefined,
});
}
handleCommentSortChange(sort: CommentSortType) {
this.updateUrl({
sort: commentToPostSortType(sort),
pageCursor: undefined,
});
}
handleDataTypeChange(dataType: DataType) {
@ -641,60 +696,62 @@ export class Community extends Component<CommunityRouteProps, State> {
}));
}
async updateUrl({
dataType,
pageCursor,
sort,
showHidden,
}: Partial<CommunityProps>) {
async updateUrl(props: Partial<CommunityProps>) {
const {
dataType: urlDataType,
sort: urlSort,
showHidden: urlShowHidden,
} = this.props;
const queryParams: QueryParams<CommunityProps> = {
dataType: getDataTypeString(dataType ?? urlDataType),
pageCursor: pageCursor,
sort: sort ?? urlSort,
showHidden: showHidden ?? urlShowHidden,
dataType,
pageCursor,
sort,
showHidden,
match: {
params: { name },
},
} = {
...this.props,
...props,
};
this.props.history.push(
`/c/${this.props.match.params.name}${getQueryString(queryParams)}`,
);
const queryParams: QueryParams<CommunityProps> = {
dataType: getDataTypeString(dataType ?? DataType.Post),
pageCursor: pageCursor,
sort: sort,
showHidden: showHidden,
};
await this.fetchData();
this.props.history.push(`/c/${name}${getQueryString(queryParams)}`);
}
async fetchData() {
const { dataType, pageCursor, sort, showHidden } = this.props;
const { name } = this.props.match.params;
fetchDataToken?: symbol;
async fetchData(props: CommunityRouteProps) {
const token = (this.fetchDataToken = Symbol());
const { dataType, pageCursor, sort, showHidden } = props;
const { name } = props.match.params;
if (dataType === DataType.Post) {
this.setState({ postsRes: LOADING_REQUEST });
this.setState({
postsRes: await HttpService.client.getPosts({
page_cursor: pageCursor,
limit: fetchLimit,
sort,
type_: "All",
community_name: name,
saved_only: false,
show_hidden: showHidden === "true",
}),
this.setState({ postsRes: LOADING_REQUEST, commentsRes: EMPTY_REQUEST });
const postsRes = await HttpService.client.getPosts({
page_cursor: pageCursor,
limit: fetchLimit,
sort,
type_: "All",
community_name: name,
saved_only: false,
show_hidden: showHidden === "true",
});
if (token === this.fetchDataToken) {
this.setState({ postsRes });
}
} else {
this.setState({ commentsRes: LOADING_REQUEST });
this.setState({
commentsRes: await HttpService.client.getComments({
limit: fetchLimit,
sort: postToCommentSortType(sort),
type_: "All",
community_name: name,
saved_only: false,
}),
this.setState({ commentsRes: LOADING_REQUEST, postsRes: EMPTY_REQUEST });
const commentsRes = await HttpService.client.getComments({
limit: fetchLimit,
sort: postToCommentSortType(sort),
type_: "All",
community_name: name,
saved_only: false,
});
if (token === this.fetchDataToken) {
this.setState({ commentsRes });
}
}
}
@ -778,6 +835,9 @@ export class Community extends Component<CommunityRouteProps, State> {
const createCommentRes = await HttpService.client.createComment(form);
this.createAndUpdateComments(createCommentRes);
if (createCommentRes.state === "failed") {
toast(I18NextService.i18n.t(createCommentRes.err.message), "danger");
}
return createCommentRes;
}
@ -785,6 +845,9 @@ export class Community extends Component<CommunityRouteProps, State> {
const editCommentRes = await HttpService.client.editComment(form);
this.findAndUpdateCommentEdit(editCommentRes);
if (editCommentRes.state === "failed") {
toast(I18NextService.i18n.t(editCommentRes.err.message), "danger");
}
return editCommentRes;
}
@ -996,7 +1059,6 @@ export class Community extends Component<CommunityRouteProps, State> {
res.data.comment_view,
s.commentsRes.data.comments,
);
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
@ -1018,12 +1080,6 @@ export class Community extends Component<CommunityRouteProps, State> {
this.setState(s => {
if (s.commentsRes.state === "success" && res.state === "success") {
s.commentsRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true,
);
}
return s;
});

View file

@ -80,6 +80,21 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
this.handleEditCancel = this.handleEditCancel.bind(this);
}
unlisten = () => {};
componentWillMount() {
// Leave edit mode on navigation
this.unlisten = this.context.router.history.listen(() => {
if (this.state.showEdit) {
this.setState({ showEdit: false });
}
});
}
componentWillUnmount(): void {
this.unlisten();
}
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & SidebarProps>,
): void {

View file

@ -3,12 +3,12 @@ import { capitalizeFirstLetter, resourcesSettled } from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import { Component } from "inferno";
import {
BannedPersonsResponse,
CreateCustomEmoji,
DeleteCustomEmoji,
EditCustomEmoji,
CreateOAuthProvider,
DeleteOAuthProvider,
EditOAuthProvider,
EditSite,
GetFederatedInstancesResponse,
GetSiteResponse,
@ -17,7 +17,6 @@ import {
PersonView,
} from "lemmy-js-client";
import { InitialFetchRequest } from "../../interfaces";
import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
import { FirstLoadService, I18NextService } from "../../services";
import {
EMPTY_REQUEST,
@ -41,6 +40,9 @@ import { IRoutePropsWithFetch } from "../../routes";
import { MediaUploads } from "../common/media-uploads";
import { Paginator } from "../common/paginator";
import { snapToTop } from "@utils/browser";
import { isBrowser } from "@utils/browser";
import ConfirmationModal from "../common/modal/confirmation-modal";
import OAuthProvidersTab from "./oauth/oauth-providers-tab";
type AdminSettingsData = RouteDataResponse<{
bannedRes: BannedPersonsResponse;
@ -51,10 +53,10 @@ type AdminSettingsData = RouteDataResponse<{
interface AdminSettingsState {
siteRes: GetSiteResponse;
banned: PersonView[];
currentTab: string;
instancesRes: RequestState<GetFederatedInstancesResponse>;
bannedRes: RequestState<BannedPersonsResponse>;
leaveAdminTeamRes: RequestState<GetSiteResponse>;
showConfirmLeaveAdmin: boolean;
uploadsRes: RequestState<ListMediaResponse>;
uploadsPage: number;
loading: boolean;
@ -79,10 +81,10 @@ export class AdminSettings extends Component<
state: AdminSettingsState = {
siteRes: this.isoData.site_res,
banned: [],
currentTab: "site",
bannedRes: EMPTY_REQUEST,
instancesRes: EMPTY_REQUEST,
leaveAdminTeamRes: EMPTY_REQUEST,
showConfirmLeaveAdmin: false,
uploadsRes: EMPTY_REQUEST,
uploadsPage: 1,
loading: false,
@ -102,10 +104,13 @@ export class AdminSettings extends Component<
super(props, context);
this.handleEditSite = this.handleEditSite.bind(this);
this.handleEditEmoji = this.handleEditEmoji.bind(this);
this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
this.handleUploadsPageChange = this.handleUploadsPageChange.bind(this);
this.handleToggleShowLeaveAdminConfirmation =
this.handleToggleShowLeaveAdminConfirmation.bind(this);
this.handleLeaveAdminTeam = this.handleLeaveAdminTeam.bind(this);
this.handleEditOAuthProvider = this.handleEditOAuthProvider.bind(this);
this.handleDeleteOAuthProvider = this.handleDeleteOAuthProvider.bind(this);
this.handleCreateOAuthProvider = this.handleCreateOAuthProvider.bind(this);
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
@ -134,12 +139,14 @@ export class AdminSettings extends Component<
};
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
await this.fetchData();
} else {
const themeList = await fetchThemeList();
this.setState({ themeList });
async componentWillMount() {
if (isBrowser()) {
if (!this.state.isIsomorphic) {
await this.fetchData();
} else {
const themeList = await fetchThemeList();
this.setState({ themeList });
}
}
}
@ -242,11 +249,7 @@ export class AdminSettings extends Component<
id="taglines-tab-pane"
>
<div className="row">
<TaglineForm
taglines={this.state.siteRes.taglines}
onSaveSite={this.handleEditSite}
loading={this.state.loading}
/>
<TaglineForm />
</div>
</div>
),
@ -263,11 +266,7 @@ export class AdminSettings extends Component<
id="emojis-tab-pane"
>
<div className="row">
<EmojiForm
onCreate={this.handleCreateEmoji}
onDelete={this.handleDeleteEmoji}
onEdit={this.handleEditEmoji}
/>
<EmojiForm />
</div>
</div>
),
@ -287,6 +286,28 @@ export class AdminSettings extends Component<
</div>
),
},
{
key: "auth",
label: I18NextService.i18n.t("authentication"),
getNode: isSelected => (
<div
className={classNames("tab-pane", {
active: isSelected,
})}
role="tabpanel"
id="auth-tab-pane"
>
<OAuthProvidersTab
oauthProviders={
this.state.siteRes.admin_oauth_providers ?? []
}
onCreate={this.handleCreateOAuthProvider}
onDelete={this.handleDeleteOAuthProvider}
onEdit={this.handleEditOAuthProvider}
/>
</div>
),
},
]}
/>
</div>
@ -339,6 +360,13 @@ export class AdminSettings extends Component<
))}
</ul>
{this.leaveAdmin()}
<ConfirmationModal
message={I18NextService.i18n.t("leave_admin_team_confirmation")}
loadingMessage={I18NextService.i18n.t("leaving_admin_team")}
onNo={this.handleToggleShowLeaveAdminConfirmation}
onYes={this.handleLeaveAdminTeam}
show={this.state.showConfirmLeaveAdmin}
/>
</>
);
}
@ -346,7 +374,7 @@ export class AdminSettings extends Component<
leaveAdmin() {
return (
<button
onClick={linkEvent(this, this.handleLeaveAdminTeam)}
onClick={this.handleToggleShowLeaveAdminConfirmation}
className="btn btn-danger mb-2"
>
{this.state.leaveAdminTeamRes.state === "loading" ? (
@ -417,10 +445,12 @@ export class AdminSettings extends Component<
this.setState(s => {
s.siteRes.site_view = editRes.data.site_view;
// TODO: Where to get taglines from?
s.siteRes.taglines = editRes.data.taglines;
return s;
});
toast(I18NextService.i18n.t("site_saved"));
// You need to reload the page, to properly update the siteRes everywhere
setTimeout(() => location.reload(), 500);
}
this.setState({ loading: false });
@ -428,46 +458,90 @@ export class AdminSettings extends Component<
return editRes;
}
handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
i.ctx.setState({ currentTab: i.tab });
handleToggleShowLeaveAdminConfirmation() {
this.setState(prev => ({
showConfirmLeaveAdmin: !prev.showConfirmLeaveAdmin,
}));
}
async handleLeaveAdminTeam(i: AdminSettings) {
i.setState({ leaveAdminTeamRes: LOADING_REQUEST });
async handleLeaveAdminTeam() {
this.setState({ leaveAdminTeamRes: LOADING_REQUEST });
this.setState({
leaveAdminTeamRes: await HttpService.client.leaveAdmin(),
});
if (this.state.leaveAdminTeamRes.state === "success") {
toast(I18NextService.i18n.t("left_admin_team"));
this.setState({ showConfirmLeaveAdmin: false });
this.context.router.history.replace("/");
}
}
async handleEditEmoji(form: EditCustomEmoji) {
const res = await HttpService.client.editCustomEmoji(form);
if (res.state === "success") {
updateEmojiDataModel(res.data.custom_emoji);
}
}
async handleDeleteEmoji(form: DeleteCustomEmoji) {
const res = await HttpService.client.deleteCustomEmoji(form);
if (res.state === "success") {
removeFromEmojiDataModel(form.id);
}
}
async handleCreateEmoji(form: CreateCustomEmoji) {
const res = await HttpService.client.createCustomEmoji(form);
if (res.state === "success") {
updateEmojiDataModel(res.data.custom_emoji);
}
}
async handleUploadsPageChange(val: number) {
this.setState({ uploadsPage: val });
snapToTop();
await this.fetchUploadsOnly();
}
async handleEditOAuthProvider(form: EditOAuthProvider) {
this.setState({ loading: true });
const res = await HttpService.client.editOAuthProvider(form);
if (res.state === "success") {
const newOAuthProvider = res.data;
this.setState(s => {
s.siteRes.admin_oauth_providers = (
s.siteRes.admin_oauth_providers ?? []
).map(p => {
return p?.id === newOAuthProvider.id ? newOAuthProvider : p;
});
return s;
});
toast(I18NextService.i18n.t("site_saved"));
} else {
toast(I18NextService.i18n.t("couldnt_edit_oauth_provider"), "danger");
}
this.setState({ loading: false });
}
async handleDeleteOAuthProvider(form: DeleteOAuthProvider) {
this.setState({ loading: true });
const res = await HttpService.client.deleteOAuthProvider(form);
if (res.state === "success") {
this.setState(s => {
s.siteRes.admin_oauth_providers = (
s.siteRes.admin_oauth_providers ?? []
).filter(p => p.id !== form.id);
return s;
});
toast(I18NextService.i18n.t("site_saved"));
} else {
toast(I18NextService.i18n.t("couldnt_delete_oauth_provider"), "danger");
}
this.setState({ loading: false });
}
async handleCreateOAuthProvider(form: CreateOAuthProvider) {
this.setState({ loading: true });
const res = await HttpService.client.createOAuthProvider(form);
if (res.state === "success") {
this.setState(s => {
s.siteRes.admin_oauth_providers = [
...(s.siteRes.admin_oauth_providers ?? []),
res.data,
];
});
toast(I18NextService.i18n.t("site_saved"));
} else {
toast(I18NextService.i18n.t("couldnt_create_oauth_provider"), "danger");
}
this.setState({ loading: false });
}
}

View file

@ -1,79 +1,81 @@
import { setIsoData } from "@utils/app";
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import {
CreateCustomEmoji,
DeleteCustomEmoji,
EditCustomEmoji,
GetSiteResponse,
} from "lemmy-js-client";
import { customEmojisLookup } from "../../markdown";
import { CustomEmojiView } from "lemmy-js-client";
import { emojiMartCategories, EmojiMartCategory } from "../../markdown";
import { HttpService, I18NextService } from "../../services";
import { pictrsDeleteToast, toast } from "../../toast";
import { EmojiMart } from "../common/emoji-mart";
import { Icon, Spinner } from "../common/icon";
import { Paginator } from "../common/paginator";
import { tippyMixin } from "../mixins/tippy-mixin";
import { isBrowser } from "@utils/browser";
import classNames from "classnames";
import { amAdmin } from "@utils/roles";
import { Prompt } from "inferno-router";
interface EmojiFormProps {
onEdit(form: EditCustomEmoji): void;
onCreate(form: CreateCustomEmoji): void;
onDelete(form: DeleteCustomEmoji): void;
interface EditableEmoji {
change?: "update" | "delete" | "create";
emoji: CustomEmojiView;
loading?: boolean;
}
function markForUpdate(editable: EditableEmoji) {
if (editable.change !== "create") {
editable.change = "update";
}
}
interface EmojiFormState {
siteRes: GetSiteResponse;
customEmojis: CustomEmojiViewForm[];
page: number;
}
interface CustomEmojiViewForm {
id: number;
category: string;
shortcode: string;
image_url: string;
alt_text: string;
keywords: string;
changed: boolean;
emojis: EditableEmoji[]; // Emojis for the current page
allEmojis: CustomEmojiView[]; // All emojis for emoji lookup across pages
emojiMartCustom: EmojiMartCategory[];
emojiMartKey: number;
page: number;
loading: boolean;
}
@tippyMixin
export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
private isoData = setIsoData(this.context);
export class EmojiForm extends Component<Record<never, never>, EmojiFormState> {
private itemsPerPage = 15;
private emptyState: EmojiFormState = {
siteRes: this.isoData.site_res,
customEmojis: this.isoData.site_res.custom_emojis.map((x, index) => ({
id: x.custom_emoji.id,
category: x.custom_emoji.category,
shortcode: x.custom_emoji.shortcode,
image_url: x.custom_emoji.image_url,
alt_text: x.custom_emoji.alt_text,
keywords: x.keywords.map(x => x.keyword).join(" "),
changed: false,
page: 1 + Math.floor(index / this.itemsPerPage),
loading: false,
})),
private needsRefetch = true;
state: EmojiFormState = {
emojis: [],
allEmojis: [],
emojiMartCustom: [],
emojiMartKey: 1,
loading: false,
page: 1,
};
state: EmojiFormState;
private scrollRef: any = {};
constructor(props: any, context: any) {
super(props, context);
this.state = this.emptyState;
this.handlePageChange = this.handlePageChange.bind(this);
this.handleEmojiClick = this.handleEmojiClick.bind(this);
}
async componentWillMount() {
if (isBrowser()) {
this.handlePageChange(1);
}
}
hasPendingChanges() {
return this.state.emojis.some(x => x.change);
}
render() {
return (
<div className="home-emojis-form col-12">
<Prompt
message={I18NextService.i18n.t("block_leaving")}
when={this.hasPendingChanges()}
/>
<h1 className="h4 mb-4">{I18NextService.i18n.t("custom_emojis")}</h1>
{customEmojisLookup.size > 0 && (
{this.state.emojiMartCustom.length > 0 && (
<div>
<EmojiMart
key={this.state.emojiMartKey}
onEmojiClick={this.handleEmojiClick}
pickerOptions={this.configurePicker()}
></EmojiMart>
@ -87,6 +89,10 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
<thead className="pointer">
<tr>
<th>{I18NextService.i18n.t("column_emoji")}</th>
<th
className="text-right"
// Upload button
/>
<th className="text-right">
{I18NextService.i18n.t("column_shortcode")}
</th>
@ -102,20 +108,15 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
<th className="text-right d-lg-table-cell">
{I18NextService.i18n.t("column_keywords")}
</th>
<th></th>
<th style="width:121px"></th>
</tr>
</thead>
<tbody>
{this.state.customEmojis
.slice(
Number((this.state.page - 1) * this.itemsPerPage),
Number(
(this.state.page - 1) * this.itemsPerPage +
this.itemsPerPage,
),
)
.map((cv, index) => (
<tr key={index} ref={e => (this.scrollRef[cv.shortcode] = e)}>
{this.state.emojis.map((editable: EditableEmoji, index) => {
const cv = editable.emoji.custom_emoji;
return (
<tr key={index}>
<td style="text-align:center;">
{cv.image_url.length > 0 && (
<img
@ -124,7 +125,9 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
alt={cv.alt_text}
/>
)}
{cv.image_url.length === 0 && (
</td>
<td>
{
<label
// TODO: Fix this linting violation
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
@ -150,7 +153,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
)}
/>
</label>
)}
}
</td>
<td className="text-right">
<input
@ -167,6 +170,7 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
</td>
<td className="text-right">
<input
ref={e => (this.scrollRef[cv.shortcode] = e)}
type="text"
placeholder="Category"
className="form-control"
@ -206,31 +210,54 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
type="text"
placeholder="Keywords"
className="form-control"
value={cv.keywords}
value={editable.emoji.keywords
.map(k => k.keyword)
.join(" ")}
onInput={linkEvent(
{ form: this, index: index },
this.handleEmojiKeywordChange,
)}
/>
</td>
<td
className={classNames("", {
"border-info": editable.change === "update",
"border-danger": editable.change === "delete",
"border-warning": editable.change === "create",
})}
>
{editable.change === "update" && (
<span>
<Icon icon="transfer" />
</span>
)}
{editable.change === "delete" && (
<span>
<Icon icon="trash" />
</span>
)}
{editable.change === "create" && (
<span>
<Icon icon="add" />
</span>
)}
</td>
<td>
<div>
<span title={this.getEditTooltip(cv)}>
<div class="row flex-nowrap g-0">
<span class="col" title={this.getEditTooltip(editable)}>
<button
className={
(this.canEdit(cv)
? "text-success "
: "text-muted ") + "btn btn-link btn-animate"
}
className={classNames("btn btn-link btn-animate", {
"text-success": this.canSave(editable),
})}
onClick={linkEvent(
{ i: this, cv: cv },
this.handleEditEmojiClick,
{ i: this, cv: editable },
this.handleSaveEmojiClick,
)}
data-tippy-content={I18NextService.i18n.t("save")}
aria-label={I18NextService.i18n.t("save")}
disabled={!this.canEdit(cv)}
disabled={!this.canSave(editable)}
>
{cv.loading ? (
{editable.loading ? (
<Spinner />
) : (
capitalizeFirstLetter(
@ -240,14 +267,14 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
</button>
</span>
<button
className="btn btn-link btn-animate text-muted"
className="col btn btn-link btn-animate text-muted"
onClick={linkEvent(
{ i: this, index: index, cv: cv },
{ i: this, index: index, cv: editable },
this.handleDeleteEmojiClick,
)}
data-tippy-content={I18NextService.i18n.t("delete")}
aria-label={I18NextService.i18n.t("delete")}
disabled={cv.loading}
disabled={editable.loading}
title={I18NextService.i18n.t("delete")}
>
<Icon
@ -255,10 +282,28 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
classes="icon-inline text-danger"
/>
</button>
<button
className={classNames(
"col btn btn-link btn-animate",
{
"text-danger": !!editable.change,
},
)}
onClick={linkEvent(
{ i: this, cv: editable },
this.handleCancelEmojiClick,
)}
data-tippy-content={I18NextService.i18n.t("cancel")}
aria-label={I18NextService.i18n.t("cancel")}
disabled={!editable.change}
>
{I18NextService.i18n.t("cancel")}
</button>
</div>
</td>
</tr>
))}
);
})}
</tbody>
</table>
<br />
@ -273,43 +318,91 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
page={this.state.page}
onChange={this.handlePageChange}
nextDisabled={false}
disabled={this.hasPendingChanges()}
/>
</div>
</div>
);
}
canEdit(cv: CustomEmojiViewForm) {
const noEmptyFields =
cv.alt_text.length > 0 &&
cv.category.length > 0 &&
cv.image_url.length > 0 &&
cv.shortcode.length > 0;
const noDuplicateShortCodes =
this.state.customEmojis.filter(
x => x.shortcode === cv.shortcode && x.id !== cv.id,
).length === 0;
return noEmptyFields && noDuplicateShortCodes && !cv.loading && cv.changed;
canSave(cv: EditableEmoji) {
const requiredFields =
cv.emoji.custom_emoji.image_url.length > 0 &&
cv.emoji.custom_emoji.shortcode.length > 0;
return requiredFields && !cv.loading;
}
getEditTooltip(cv: CustomEmojiViewForm) {
if (this.canEdit(cv)) return I18NextService.i18n.t("save");
getEditTooltip(cv: EditableEmoji) {
if (this.canSave(cv)) return I18NextService.i18n.t("save");
else return I18NextService.i18n.t("custom_emoji_save_validation");
}
handlePageChange(page: number) {
this.setState({ page: page });
async handlePageChange(page: number) {
this.setState({ loading: true });
let allEmojis: CustomEmojiView[] = this.state.allEmojis;
let emojiMartCustom: EmojiMartCategory[] = this.state.emojiMartCustom;
let emojiMartKey: number = this.state.emojiMartKey;
if (this.needsRefetch) {
const emojiRes = await HttpService.client.listCustomEmojis({
ignore_page_limits: true,
});
if (emojiRes.state === "success") {
this.needsRefetch = false;
allEmojis = emojiRes.data.custom_emojis;
allEmojis.sort((a, b) => {
const categoryOrder = a.custom_emoji.category.localeCompare(
b.custom_emoji.category,
);
if (categoryOrder === 0) {
return a.custom_emoji.shortcode.localeCompare(
b.custom_emoji.shortcode,
);
}
return categoryOrder;
});
}
emojiMartCustom = emojiMartCategories(allEmojis);
emojiMartKey++;
}
if (allEmojis) {
const startIndex = (page - 1) * this.itemsPerPage;
const emojis = allEmojis
.slice(startIndex, startIndex + this.itemsPerPage)
.map(x => ({ emoji: structuredClone(x) })); // clone for restore after cancel
this.setState({
loading: false,
allEmojis,
emojiMartCustom,
emojiMartKey,
emojis,
page,
});
} else {
this.setState({ loading: false, page });
}
}
handleEmojiClick(e: any) {
const view = customEmojisLookup.get(e.id);
if (view) {
const page = this.state.customEmojis.find(
x => x.id === view.custom_emoji.id,
)?.page;
if (page) {
this.setState({ page: page });
this.scrollRef[view.custom_emoji.shortcode].scrollIntoView();
async handleEmojiClick(e: any) {
const emojiIndex = this.state.allEmojis.findIndex(
x => x.custom_emoji.shortcode === e.id,
);
if (emojiIndex >= 0) {
const { shortcode } = this.state.allEmojis[emojiIndex].custom_emoji;
const page = Math.floor(emojiIndex / this.itemsPerPage) + 1;
if (page !== this.state.page) {
if (
this.hasPendingChanges() &&
!confirm(I18NextService.i18n.t("block_leaving"))
) {
return;
}
await this.handlePageChange(page);
await new Promise(r => setTimeout(r));
}
if (shortcode) {
const categoryInput: HTMLInputElement | undefined =
this.scrollRef[shortcode];
categoryInput?.focus();
}
}
}
@ -318,32 +411,22 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
props: { form: EmojiForm; index: number },
event: any,
) {
const custom_emojis = [...props.form.state.customEmojis];
const pagedIndex =
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
const item = {
...props.form.state.customEmojis[pagedIndex],
category: event.target.value,
changed: true,
};
custom_emojis[Number(pagedIndex)] = item;
props.form.setState({ customEmojis: custom_emojis });
const editable: EditableEmoji = props.form.state.emojis[props.index];
props.form.setState(() => {
markForUpdate(editable);
editable.emoji.custom_emoji.category = event.target.value;
});
}
handleEmojiShortCodeChange(
props: { form: EmojiForm; index: number },
event: any,
) {
const custom_emojis = [...props.form.state.customEmojis];
const pagedIndex =
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
const item = {
...props.form.state.customEmojis[pagedIndex],
shortcode: event.target.value,
changed: true,
};
custom_emojis[Number(pagedIndex)] = item;
props.form.setState({ customEmojis: custom_emojis });
const editable: EditableEmoji = props.form.state.emojis[props.index];
props.form.setState(() => {
markForUpdate(editable);
editable.emoji.custom_emoji.shortcode = event.target.value;
});
}
handleEmojiImageUrlChange(
@ -354,28 +437,11 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
}: { form: EmojiForm; index: number; overrideValue: string | null },
event: any,
) {
form.setState(prevState => {
const custom_emojis = [...form.state.customEmojis];
const pagedIndex = (form.state.page - 1) * form.itemsPerPage + index;
const item = {
...form.state.customEmojis[pagedIndex],
image_url: overrideValue ?? event.target.value,
changed: true,
};
custom_emojis[Number(pagedIndex)] = item;
return {
...prevState,
customEmojis: prevState.customEmojis.map((ce, i) =>
i === pagedIndex
? {
...ce,
image_url: overrideValue ?? event.target.value,
changed: true,
loading: false,
}
: ce,
),
};
const editable: EditableEmoji = form.state.emojis[index];
form.setState(() => {
markForUpdate(editable);
editable.emoji.custom_emoji.image_url =
overrideValue ?? event.target.value;
});
}
@ -383,97 +449,117 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
props: { form: EmojiForm; index: number },
event: any,
) {
const custom_emojis = [...props.form.state.customEmojis];
const pagedIndex =
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
const item = {
...props.form.state.customEmojis[pagedIndex],
alt_text: event.target.value,
changed: true,
};
custom_emojis[Number(pagedIndex)] = item;
props.form.setState({ customEmojis: custom_emojis });
const editable: EditableEmoji = props.form.state.emojis[props.index];
props.form.setState(() => {
markForUpdate(editable);
editable.emoji.custom_emoji.alt_text = event.target.value;
});
}
handleEmojiKeywordChange(
props: { form: EmojiForm; index: number },
event: any,
) {
const custom_emojis = [...props.form.state.customEmojis];
const pagedIndex =
(props.form.state.page - 1) * props.form.itemsPerPage + props.index;
const item = {
...props.form.state.customEmojis[pagedIndex],
keywords: event.target.value,
changed: true,
};
custom_emojis[Number(pagedIndex)] = item;
props.form.setState({ customEmojis: custom_emojis });
const editable: EditableEmoji = props.form.state.emojis[props.index];
props.form.setState(() => {
markForUpdate(editable);
editable.emoji.keywords = event.target.value
.split(" ")
.map((x: string) => ({ id: -1, keyword: x }));
});
}
handleDeleteEmojiClick(d: {
i: EmojiForm;
index: number;
cv: CustomEmojiViewForm;
cv: EditableEmoji;
}) {
const pagedIndex = (d.i.state.page - 1) * d.i.itemsPerPage + d.index;
if (d.cv.id !== 0) {
d.i.props.onDelete({
id: d.cv.id,
});
if (d.cv.change === "create") {
// This drops the entry immediately, other deletes have to be saved.
d.i.setState(prev => ({
emojis: prev.emojis.filter(x => x !== d.cv),
}));
} else {
const custom_emojis = [...d.i.state.customEmojis];
custom_emojis.splice(Number(pagedIndex), 1);
d.i.setState({ customEmojis: custom_emojis });
}
}
handleEditEmojiClick(d: { i: EmojiForm; cv: CustomEmojiViewForm }) {
const keywords = d.cv.keywords
.split(" ")
.filter(x => x.length > 0) as string[];
const uniqueKeywords = Array.from(new Set(keywords));
if (d.cv.id !== 0) {
d.i.props.onEdit({
id: d.cv.id,
category: d.cv.category,
image_url: d.cv.image_url,
alt_text: d.cv.alt_text,
keywords: uniqueKeywords,
});
} else {
d.i.props.onCreate({
category: d.cv.category,
shortcode: d.cv.shortcode,
image_url: d.cv.image_url,
alt_text: d.cv.alt_text,
keywords: uniqueKeywords,
d.i.setState(() => {
d.cv.change = "delete";
});
}
}
handleAddEmojiClick(form: EmojiForm, event: any) {
async handleSaveEmojiClick(d: { i: EmojiForm; cv: EditableEmoji }) {
d.i.needsRefetch = true;
const editable = d.cv;
if (editable.change === "update") {
const resp = await HttpService.client.editCustomEmoji({
...editable.emoji.custom_emoji,
keywords: editable.emoji.keywords.map(x => x.keyword),
});
if (resp.state === "success") {
d.i.setState(() => {
editable.emoji = resp.data.custom_emoji;
editable.change = undefined;
});
}
} else if (editable.change === "delete") {
const resp = await HttpService.client.deleteCustomEmoji(
editable.emoji.custom_emoji,
);
if (resp.state === "success") {
d.i.setState(prev => ({
emojis: prev.emojis.filter(x => x !== editable),
}));
}
} else if (editable.change === "create") {
const resp = await HttpService.client.createCustomEmoji({
...editable.emoji.custom_emoji,
keywords: editable.emoji.keywords.map(x => x.keyword),
});
if (resp.state === "success") {
d.i.setState(() => {
editable.emoji = resp.data.custom_emoji;
editable.change = undefined;
});
}
}
}
async handleCancelEmojiClick(d: { i: EmojiForm; cv: EditableEmoji }) {
if (d.cv.change === "create") {
d.i.setState(() => {
return {
emojis: d.i.state.emojis.filter(x => x !== d.cv),
};
});
} else if (d.cv.change === "update" || d.cv.change === "delete") {
const original = d.i.state.allEmojis.find(
x => x.custom_emoji.id === d.cv.emoji.custom_emoji.id,
);
if (original) {
d.i.setState(() => {
d.cv.emoji = structuredClone(original);
d.cv.change = undefined;
});
}
}
}
async handleAddEmojiClick(form: EmojiForm, event: any) {
event.preventDefault();
form.setState(prevState => {
const page =
1 + Math.floor(prevState.customEmojis.length / form.itemsPerPage);
const item: CustomEmojiViewForm = {
id: 0,
shortcode: "",
alt_text: "",
category: "",
image_url: "",
keywords: "",
changed: false,
page: page,
loading: false,
};
return {
...prevState,
customEmojis: [...prevState.customEmojis, item],
page,
};
form.setState(prev => {
prev.emojis.push({
emoji: {
custom_emoji: {
id: -1,
published: "",
category: "",
shortcode: "",
image_url: "",
alt_text: "",
},
keywords: [],
},
change: "create",
});
});
}
@ -489,16 +575,15 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
file = event;
}
form.setState(prevState => ({
...prevState,
customEmojis: prevState.customEmojis.map((cv, i) =>
i === index ? { ...cv, loading: true } : cv,
),
}));
const editable = form.state.emojis[index];
form.setState(() => {
editable.loading = true;
});
HttpService.client.uploadImage({ image: file }).then(res => {
console.log("pictrs upload:");
console.log(res);
form.setState(() => {
editable.loading = false;
});
if (res.state === "success") {
if (res.data.msg === "ok") {
pictrsDeleteToast(file.name, res.data.delete_url as string);
@ -519,10 +604,20 @@ export class EmojiForm extends Component<EmojiFormProps, EmojiFormState> {
}
configurePicker(): any {
const custom = this.state.emojiMartCustom;
if (process.env["NODE_ENV"] === "development") {
// Once an emoji-mart Picker is initialized with these options, other
// instances also only show the custom emojis.
console.assert(
amAdmin(),
"EmojiMart doesn't deal well with differently configured instances.",
);
}
return {
data: { categories: [], emojis: [], aliases: [] },
maxFrequentRows: 0,
dynamicWidth: true,
custom,
};
}
}

View file

@ -1,11 +1,11 @@
import {
commentsToFlatNodes,
commentToPostSortType,
editComment,
editPost,
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
getDataTypeString,
myAuth,
postToCommentSortType,
@ -17,15 +17,13 @@ import {
import {
getQueryParams,
getQueryString,
getRandomFromList,
resourcesSettled,
} from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { canCreateCommunity } from "@utils/roles";
import type { QueryParams, StringBoolean } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import { NoOptionI18nKeys } from "i18next";
import { Component, MouseEventHandler, linkEvent } from "inferno";
import { Component, InfernoNode, MouseEventHandler, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
import { Link } from "inferno-router";
import {
@ -36,7 +34,6 @@ import {
BanPerson,
BanPersonResponse,
BlockPerson,
CommentId,
CommentReplyResponse,
CommentResponse,
CreateComment,
@ -57,8 +54,6 @@ import {
GetSiteResponse,
HidePost,
LemmyHttp,
ListCommunities,
ListCommunitiesResponse,
ListingType,
LockPost,
MarkCommentReplyAsRead,
@ -72,11 +67,12 @@ import {
RemovePost,
SaveComment,
SavePost,
SortType,
PostSortType,
SuccessResponse,
TransferCommunity,
CommentSortType,
} from "lemmy-js-client";
import { fetchLimit, relTags, trendingFetchLimit } from "../../config";
import { fetchLimit, relTags } from "../../config";
import {
CommentViewType,
DataType,
@ -107,31 +103,28 @@ import { getHttpBaseInternal } from "../../utils/env";
import {
CommentsLoadingSkeleton,
PostsLoadingSkeleton,
TrendingCommunitiesLoadingSkeleton,
} from "../common/loading-skeleton";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import PostHiddenSelect from "../common/post-hidden-select";
import { snapToTop } from "@utils/browser";
import { isBrowser, snapToTop } from "@utils/browser";
import { CommentSortSelect } from "../common/comment-sort-select";
interface HomeState {
postsRes: RequestState<GetPostsResponse>;
commentsRes: RequestState<GetCommentsResponse>;
trendingCommunitiesRes: RequestState<ListCommunitiesResponse>;
showSubscribedMobile: boolean;
showTrendingMobile: boolean;
showSidebarMobile: boolean;
subscribedCollapsed: boolean;
tagline?: string;
siteRes: GetSiteResponse;
finished: Map<CommentId, boolean | undefined>;
isIsomorphic: boolean;
}
interface HomeProps {
listingType?: ListingType;
dataType: DataType;
sort: SortType;
sort: PostSortType;
pageCursor?: PaginationCursor;
showHidden?: StringBoolean;
}
@ -139,10 +132,9 @@ interface HomeProps {
type HomeData = RouteDataResponse<{
postsRes: GetPostsResponse;
commentsRes: GetCommentsResponse;
trendingCommunitiesRes: ListCommunitiesResponse;
}>;
function getRss(listingType: ListingType, sort: SortType) {
function getRss(listingType: ListingType, sort: PostSortType) {
let rss: string | undefined = undefined;
const queryString = getQueryString({ sort });
@ -187,13 +179,13 @@ function getListingTypeFromQuery(
function getSortTypeFromQuery(
type: string | undefined,
fallback: SortType,
): SortType {
return type ? (type as SortType) : fallback;
fallback: PostSortType,
): PostSortType {
return type ? (type as PostSortType) : fallback;
}
type Fallbacks = {
sort: SortType;
sort: PostSortType;
listingType: ListingType;
};
@ -214,7 +206,8 @@ export function getHomeQueryParams(
},
source,
{
sort: local_user?.default_sort_type ?? local_site.default_sort_type,
sort:
local_user?.default_post_sort_type ?? local_site.default_post_sort_type,
listingType:
local_user?.default_listing_type ??
local_site.default_post_listing_type,
@ -240,18 +233,6 @@ const MobileButton = ({
</button>
);
const LinkButton = ({
path,
translationKey,
}: {
path: string;
translationKey: NoOptionI18nKeys;
}) => (
<Link className="btn btn-secondary d-block" to={path}>
{I18NextService.i18n.t(translationKey)}
</Link>
);
type HomePathProps = Record<string, never>;
type HomeRouteProps = RouteComponentProps<HomePathProps> & HomeProps;
export type HomeFetchConfig = IRoutePropsWithFetch<
@ -267,19 +248,15 @@ export class Home extends Component<HomeRouteProps, HomeState> {
state: HomeState = {
postsRes: EMPTY_REQUEST,
commentsRes: EMPTY_REQUEST,
trendingCommunitiesRes: EMPTY_REQUEST,
siteRes: this.isoData.site_res,
showSubscribedMobile: false,
showTrendingMobile: false,
showSidebarMobile: false,
subscribedCollapsed: false,
finished: new Map(),
isIsomorphic: false,
};
loadingSettled(): boolean {
return resourcesSettled([
this.state.trendingCommunitiesRes,
this.props.dataType === DataType.Post
? this.state.postsRes
: this.state.commentsRes,
@ -290,6 +267,7 @@ export class Home extends Component<HomeRouteProps, HomeState> {
super(props, context);
this.handleSortChange = this.handleSortChange.bind(this);
this.handleCommentSortChange = this.handleCommentSortChange.bind(this);
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
this.handleDataTypeChange = this.handleDataTypeChange.bind(this);
this.handleShowHiddenChange = this.handleShowHiddenChange.bind(this);
@ -327,34 +305,37 @@ export class Home extends Component<HomeRouteProps, HomeState> {
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
const { trendingCommunitiesRes, commentsRes, postsRes } =
this.isoData.routeData;
const { commentsRes, postsRes } = this.isoData.routeData;
this.state = {
...this.state,
trendingCommunitiesRes,
commentsRes,
postsRes,
isIsomorphic: true,
};
}
this.state.tagline = getRandomFromList(
this.state?.siteRes?.taglines ?? [],
)?.content;
this.state.tagline = this.state?.siteRes?.tagline?.content;
}
async componentDidMount() {
async componentWillMount() {
if (
!this.state.isIsomorphic ||
!Object.values(this.isoData.routeData).some(
res => res.state === "success" || res.state === "failed",
)
(!this.state.isIsomorphic ||
!Object.values(this.isoData.routeData).some(
res => res.state === "success" || res.state === "failed",
)) &&
isBrowser()
) {
await Promise.all([this.fetchTrendingCommunities(), this.fetchData()]);
await this.fetchData(this.props);
}
}
componentWillReceiveProps(
nextProps: HomeRouteProps & { children?: InfernoNode },
) {
this.fetchData(nextProps);
}
static async fetchInitialData({
query: { listingType, dataType, sort, pageCursor, showHidden },
headers,
@ -390,24 +371,12 @@ export class Home extends Component<HomeRouteProps, HomeState> {
commentsFetch = client.getComments(getCommentsForm);
}
const trendingCommunitiesForm: ListCommunities = {
type_: "Local",
sort: "Hot",
limit: trendingFetchLimit,
};
const trendingCommunitiesFetch = client.listCommunities(
trendingCommunitiesForm,
);
const [postsRes, commentsRes, trendingCommunitiesRes] = await Promise.all([
const [postsRes, commentsRes] = await Promise.all([
postsFetch,
commentsFetch,
trendingCommunitiesFetch,
]);
return {
trendingCommunitiesRes,
commentsRes,
postsRes,
};
@ -470,7 +439,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
admins,
},
showSubscribedMobile,
showTrendingMobile,
showSidebarMobile,
} = this.state;
@ -484,11 +452,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
onClick={linkEvent(this, this.handleShowSubscribedMobile)}
/>
)}
<MobileButton
textKey="trending"
show={showTrendingMobile}
onClick={linkEvent(this, this.handleShowTrendingMobile)}
/>
<MobileButton
textKey="sidebar"
show={showSidebarMobile}
@ -503,11 +466,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
isMobile={true}
/>
)}
{showTrendingMobile && (
<div className="card border-secondary mb-3">
{this.trendingCommunities()}
</div>
)}
{showSubscribedMobile && (
<div className="card border-secondary mb-3">
{this.subscribedCommunities(true)}
@ -528,9 +486,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
return (
<div id="sidebarContainer">
<section id="sidebarMain" className="card border-secondary mb-3">
{this.trendingCommunities()}
</section>
<SiteSidebar
site={site}
admins={admins}
@ -551,51 +506,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
);
}
trendingCommunities() {
switch (this.state.trendingCommunitiesRes?.state) {
case "loading":
return <TrendingCommunitiesLoadingSkeleton itemCount={5} />;
case "success": {
const trending = this.state.trendingCommunitiesRes.data.communities;
return (
<>
<header className="card-header d-flex align-items-center">
<h5 className="mb-0">
<T i18nKey="trending_communities">
#
<Link className="text-body" to="/communities">
#
</Link>
</T>
</h5>
</header>
<div className="card-body">
{trending.length > 0 && (
<ul className="list-inline">
{trending.map(cv => (
<li key={cv.community.id} className="list-inline-item">
<CommunityLink community={cv.community} />
</li>
))}
</ul>
)}
{canCreateCommunity(this.state.siteRes) && (
<LinkButton
path="/create_community"
translationKey="create_a_community"
/>
)}
<LinkButton
path="/communities"
translationKey="explore_communities"
/>
</div>
</>
);
}
}
}
subscribedCommunities(isMobile = false) {
const { subscribedCollapsed } = this.state;
@ -661,34 +571,23 @@ export class Home extends Component<HomeRouteProps, HomeState> {
);
}
async updateUrl({
dataType,
listingType,
pageCursor,
sort,
showHidden,
}: Partial<HomeProps>) {
const {
dataType: urlDataType,
listingType: urlListingType,
sort: urlSort,
showHidden: urlShowHidden,
} = this.props;
async updateUrl(props: Partial<HomeProps>) {
const { dataType, listingType, pageCursor, sort, showHidden } = {
...this.props,
...props,
};
const queryParams: QueryParams<HomeProps> = {
dataType: getDataTypeString(dataType ?? urlDataType),
listingType: listingType ?? urlListingType,
dataType: getDataTypeString(dataType ?? DataType.Post),
listingType: listingType,
pageCursor: pageCursor,
sort: sort ?? urlSort,
showHidden: showHidden ?? urlShowHidden,
sort: sort,
showHidden: showHidden,
};
this.props.history.push({
pathname: "/",
search: getQueryString(queryParams),
});
await this.fetchData();
}
get posts() {
@ -766,7 +665,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
<CommentNodes
nodes={commentsToFlatNodes(comments)}
viewType={CommentViewType.Flat}
finished={this.state.finished}
isTopLevel
showCommunity
showContext
@ -830,7 +728,14 @@ export class Home extends Component<HomeRouteProps, HomeState> {
/>
</div>
<div className="col-auto">
<SortSelect sort={sort} onChange={this.handleSortChange} />
{this.props.dataType === DataType.Post ? (
<SortSelect sort={sort} onChange={this.handleSortChange} />
) : (
<CommentSortSelect
sort={postToCommentSortType(sort)}
onChange={this.handleCommentSortChange}
/>
)}
</div>
<div className="col-auto ps-0">
{getRss(
@ -843,42 +748,39 @@ export class Home extends Component<HomeRouteProps, HomeState> {
);
}
async fetchTrendingCommunities() {
this.setState({ trendingCommunitiesRes: LOADING_REQUEST });
this.setState({
trendingCommunitiesRes: await HttpService.client.listCommunities({
type_: "Local",
sort: "Hot",
limit: trendingFetchLimit,
}),
});
}
async fetchData() {
const { dataType, pageCursor, listingType, sort, showHidden } = this.props;
fetchDataToken?: symbol;
async fetchData({
dataType,
pageCursor,
listingType,
sort,
showHidden,
}: HomeProps) {
const token = (this.fetchDataToken = Symbol());
if (dataType === DataType.Post) {
this.setState({ postsRes: LOADING_REQUEST });
this.setState({
postsRes: await HttpService.client.getPosts({
page_cursor: pageCursor,
limit: fetchLimit,
sort,
saved_only: false,
type_: listingType,
show_hidden: showHidden === "true",
}),
this.setState({ postsRes: LOADING_REQUEST, commentsRes: EMPTY_REQUEST });
const postsRes = await HttpService.client.getPosts({
page_cursor: pageCursor,
limit: fetchLimit,
sort,
saved_only: false,
type_: listingType,
show_hidden: showHidden === "true",
});
if (token === this.fetchDataToken) {
this.setState({ postsRes });
}
} else {
this.setState({ commentsRes: LOADING_REQUEST });
this.setState({
commentsRes: await HttpService.client.getComments({
limit: fetchLimit,
sort: postToCommentSortType(sort),
saved_only: false,
type_: listingType,
}),
this.setState({ commentsRes: LOADING_REQUEST, postsRes: EMPTY_REQUEST });
const commentsRes = await HttpService.client.getComments({
limit: fetchLimit,
sort: postToCommentSortType(sort),
saved_only: false,
type_: listingType,
});
if (token === this.fetchDataToken) {
this.setState({ commentsRes });
}
}
}
@ -886,10 +788,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
i.setState({ showSubscribedMobile: !i.state.showSubscribedMobile });
}
handleShowTrendingMobile(i: Home) {
i.setState({ showTrendingMobile: !i.state.showTrendingMobile });
}
handleShowSidebarMobile(i: Home) {
i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
}
@ -910,10 +808,14 @@ export class Home extends Component<HomeRouteProps, HomeState> {
this.updateUrl({ pageCursor: nextPage });
}
handleSortChange(val: SortType) {
handleSortChange(val: PostSortType) {
this.updateUrl({ sort: val, pageCursor: undefined });
}
handleCommentSortChange(val: CommentSortType) {
this.updateUrl({ sort: commentToPostSortType(val), pageCursor: undefined });
}
handleListingTypeChange(val: ListingType) {
this.updateUrl({ listingType: val, pageCursor: undefined });
}
@ -923,7 +825,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
}
handleShowHiddenChange(show?: StringBoolean) {
console.log(`Got ${show}`);
this.updateUrl({
showHidden: show,
pageCursor: undefined,
@ -961,6 +862,9 @@ export class Home extends Component<HomeRouteProps, HomeState> {
const createCommentRes = await HttpService.client.createComment(form);
this.createAndUpdateComments(createCommentRes);
if (createCommentRes.state === "failed") {
toast(I18NextService.i18n.t(createCommentRes.err.message), "danger");
}
return createCommentRes;
}
@ -968,6 +872,9 @@ export class Home extends Component<HomeRouteProps, HomeState> {
const editCommentRes = await HttpService.client.editComment(form);
this.findAndUpdateCommentEdit(editCommentRes);
if (editCommentRes.state === "failed") {
toast(I18NextService.i18n.t(editCommentRes.err.message), "danger");
}
return editCommentRes;
}
@ -1156,7 +1063,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
res.data.comment_view,
s.commentsRes.data.comments,
);
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
@ -1178,12 +1084,6 @@ export class Home extends Component<HomeRouteProps, HomeState> {
this.setState(s => {
if (s.commentsRes.state === "success" && res.state === "success") {
s.commentsRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true,
);
}
return s;
});

View file

@ -26,6 +26,7 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { resourcesSettled } from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { isBrowser } from "@utils/browser";
type InstancesData = RouteDataResponse<{
federatedInstancesResponse: GetFederatedInstancesResponse;
@ -71,8 +72,8 @@ export class Instances extends Component<InstancesRouteProps, InstancesState> {
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.fetchInstances();
}
}

View file

@ -3,7 +3,12 @@ import { isBrowser, refreshTheme } from "@utils/browser";
import { getQueryParams } from "@utils/helpers";
import { Component, linkEvent } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import {
GetSiteResponse,
LoginResponse,
OAuthProvider,
PublicOAuthProvider,
} from "lemmy-js-client";
import { I18NextService, UserService } from "../../services";
import {
EMPTY_REQUEST,
@ -15,7 +20,7 @@ import { toast } from "../../toast";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import PasswordInput from "../common/password-input";
import TotpModal from "../common/totp-modal";
import TotpModal from "../common/modal/totp-modal";
import { UnreadCounterService } from "../../services";
import { RouteData } from "../../interfaces";
import { IRoutePropsWithFetch } from "../../routes";
@ -42,6 +47,7 @@ interface State {
};
siteRes: GetSiteResponse;
show2faModal: boolean;
showOAuthModal: boolean;
}
async function handleLoginSuccess(i: Login, loginRes: LoginResponse) {
@ -52,16 +58,21 @@ async function handleLoginSuccess(i: Login, loginRes: LoginResponse) {
if (site.state === "success") {
UserService.Instance.myUserInfo = site.data.my_user;
const isoData = setIsoData(i.context);
isoData.site_res.oauth_providers = site.data.oauth_providers;
isoData.site_res.admin_oauth_providers = site.data.admin_oauth_providers;
refreshTheme();
}
const { prev } = i.props;
prev
? i.props.history.replace(prev)
: i.props.history.action === "PUSH"
? i.props.history.back()
: i.props.history.replace("/");
if (prev) {
i.props.history.replace(prev);
} else if (i.props.history.action === "PUSH") {
i.props.history.back();
} else {
i.props.history.replace("/");
}
UnreadCounterService.Instance.updateAll();
}
@ -105,6 +116,45 @@ async function handleLoginSubmit(i: Login, event: any) {
}
}
export async function handleUseOAuthProvider(params: {
oauth_provider: OAuthProvider;
username?: string;
prev?: string;
answer?: string;
show_nsfw?: boolean;
}) {
const redirectUri = `${window.location.origin}/oauth/callback`;
const state = crypto.randomUUID();
const requestUri =
params.oauth_provider.authorization_endpoint +
"?" +
[
`client_id=${encodeURIComponent(params.oauth_provider.client_id)}`,
`response_type=code`,
`scope=${encodeURIComponent(params.oauth_provider.scopes)}`,
`redirect_uri=${encodeURIComponent(redirectUri)}`,
`state=${state}`,
].join("&");
// store state in local storage
localStorage.setItem(
"oauth_state",
JSON.stringify({
state,
oauth_provider_id: params.oauth_provider.id,
redirect_uri: redirectUri,
prev: params.prev ?? "/",
username: params.username,
answer: params.answer,
show_nsfw: params.show_nsfw,
expires_at: Date.now() + 5 * 60_000,
}),
);
window.location.assign(requestUri);
}
function handleLoginUsernameChange(i: Login, event: any) {
i.setState(
prevState => (prevState.form.username_or_email = event.target.value.trim()),
@ -138,12 +188,14 @@ export class Login extends Component<LoginRouteProps, State> {
},
siteRes: this.isoData.site_res,
show2faModal: false,
showOAuthModal: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handleSubmitTotp = this.handleSubmitTotp.bind(this);
this.handleLoginWithProvider = this.handleLoginWithProvider.bind(this);
}
get documentTitle(): string {
@ -172,6 +224,35 @@ export class Login extends Component<LoginRouteProps, State> {
<div className="row">
<div className="col-12 col-lg-6 offset-lg-3">{this.loginForm()}</div>
</div>
{(this.state.siteRes.oauth_providers?.length || 0) > 0 && (
<>
<div className="row mt-3 mb-2">
<div className="col-12 col-lg-6 offset-lg-3">
{I18NextService.i18n.t("or")}
</div>
</div>
<div className="row">
<div className="col col-12 col-lgl6 offset-lg-3">
<h2 className="h4 mb-3">
{I18NextService.i18n.t("oauth_login_with_provider")}
</h2>
{(this.state.siteRes.oauth_providers ?? []).map(
(provider: PublicOAuthProvider) => (
<button
className="btn btn-primary my-2 d-block"
onClick={linkEvent(
{ oauth_provider: provider },
this.handleLoginWithProvider,
)}
>
{provider.display_name}
</button>
),
)}
</div>
</div>
</>
)}
</div>
);
}
@ -194,6 +275,13 @@ export class Login extends Component<LoginRouteProps, State> {
return successful;
}
async handleLoginWithProvider(params: { oauth_provider: OAuthProvider }) {
handleUseOAuthProvider({
oauth_provider: params.oauth_provider,
prev: this.props.prev ?? "/",
});
}
loginForm() {
return (
<div>

View file

@ -0,0 +1,175 @@
import { setIsoData } from "@utils/app";
import { Component } from "inferno";
import { refreshTheme } from "@utils/browser";
import { GetSiteResponse, LoginResponse } from "lemmy-js-client";
import { Spinner } from "../../common/icon";
import { getQueryParams } from "@utils/helpers";
import { IRoutePropsWithFetch } from "../../../routes";
import { RouteData } from "../../../interfaces";
import { I18NextService, UserService } from "../../../services";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { UnreadCounterService } from "../../../services";
import { HttpService } from "../../../services/HttpService";
import { toast } from "../../../toast";
interface OAuthCallbackProps {
code?: string;
state?: string;
}
export function getOAuthCallbackQueryParams(
source?: string,
): OAuthCallbackProps {
return getQueryParams<OAuthCallbackProps>(
{
code: (code?: string) => code,
state: (state?: string) => state,
},
source,
);
}
type OAuthCallbackRouteProps = RouteComponentProps<Record<string, never>> &
OAuthCallbackProps;
export type OAuthCallbackConfig = IRoutePropsWithFetch<
RouteData,
Record<string, never>,
OAuthCallbackProps
>;
interface State {
siteRes: GetSiteResponse;
}
export class OAuthCallback extends Component<OAuthCallbackRouteProps, State> {
private isoData = setIsoData(this.context);
state: State = {
siteRes: this.isoData.site_res,
};
constructor(props: any, context: any) {
super(props, context);
}
async componentDidMount() {
// store state in local storage
const local_oauth_state = JSON.parse(
localStorage.getItem("oauth_state") || "{}",
);
if (
!(
this.props.state &&
this.props.code &&
local_oauth_state?.state &&
local_oauth_state?.oauth_provider_id &&
local_oauth_state?.expires_at &&
this.props.state === local_oauth_state.state
) ||
local_oauth_state.expires_at < Date.now()
) {
// oauth failed or expired
toast(I18NextService.i18n.t("oauth_authorization_invalid"), "danger");
this.props.history.replace("/login");
} else {
const loginRes = await HttpService.client.authenticateWithOAuth({
code: this.props.code,
oauth_provider_id: local_oauth_state.oauth_provider_id,
redirect_uri: local_oauth_state.redirect_uri,
show_nsfw: local_oauth_state.show_nsfw,
username: local_oauth_state.username,
answer: local_oauth_state.answer,
});
switch (loginRes.state) {
case "success": {
if (loginRes.data.jwt) {
handleOAuthLoginSuccess(
this,
local_oauth_state.prev,
loginRes.data,
);
} else {
if (loginRes.data.verify_email_sent) {
toast(I18NextService.i18n.t("verify_email_sent"));
}
if (loginRes.data.registration_created) {
toast(I18NextService.i18n.t("registration_application_sent"));
}
this.props.history.push("/login");
}
break;
}
case "failed": {
let err_redirect = "/login";
switch (loginRes.err.message) {
case "registration_username_required":
case "registration_application_answer_required":
err_redirect = `/signup?sso_provider_id=${local_oauth_state.oauth_provider_id}`;
toast(I18NextService.i18n.t(loginRes.err.message), "danger");
break;
case "registration_application_is_pending":
toast(
I18NextService.i18n.t("registration_application_pending"),
"danger",
);
break;
case "registration_denied":
case "oauth_authorization_invalid":
case "oauth_login_failed":
case "oauth_registration_closed":
case "email_already_exists":
case "username_already_exists":
case "no_email_setup":
toast(I18NextService.i18n.t(loginRes.err.message), "danger");
break;
default:
toast(I18NextService.i18n.t("incorrect_login"), "danger");
break;
}
this.props.history.push(err_redirect);
}
}
}
}
get documentTitle(): string {
return `${I18NextService.i18n.t("login")} - ${
this.state.siteRes.site_view.site.name
}`;
}
render() {
return (
<div className="container-lg">
<Spinner />
</div>
);
}
}
async function handleOAuthLoginSuccess(
i: OAuthCallback,
prev: string,
loginRes: LoginResponse,
) {
UserService.Instance.login({
res: loginRes,
});
const site = await HttpService.client.getSite();
if (site.state === "success") {
UserService.Instance.myUserInfo = site.data.my_user;
refreshTheme();
}
if (prev) {
i.props.history.replace(prev);
} else if (i.props.history.action === "PUSH") {
i.props.history.back();
} else {
i.props.history.replace("/");
}
UnreadCounterService.Instance.updateAll();
}

View file

@ -0,0 +1,98 @@
import { OAuthProvider } from "lemmy-js-client";
import { I18NextService } from "../../../services/I18NextService";
import { Icon } from "../../common/icon";
import { MouseEventHandler } from "inferno";
type OAuthProviderListItemProps = {
provider: OAuthProvider;
onEdit: MouseEventHandler<HTMLButtonElement>;
onDelete: MouseEventHandler<HTMLButtonElement>;
};
type TextInfoFieldProps = {
i18nKey: string;
data: string;
};
function TextInfoField({ i18nKey, data }: TextInfoFieldProps) {
return (
<div className="col overflow-auto">
<dt>{I18NextService.i18n.t(i18nKey)}</dt>
<dd className="text-truncate">{data}</dd>
</div>
);
}
function boolToYesNo(value?: boolean) {
return I18NextService.i18n.t(value ? "yes" : "no");
}
export default function OAuthProviderListItem({
provider,
onEdit,
onDelete,
}: OAuthProviderListItemProps) {
return (
<li className="oauth-item list-group-item">
<details>
<summary className="d-flex justify-content-between align-items-center">
<div className="fw-semibold">
<Icon icon="caret-right" classes="oauth-item-caret me-1" />
{provider.display_name}
</div>
<div>
<button
className="d-inline-block btn btn-outline-secondary me-2"
onClick={onEdit}
>
<Icon icon="edit" classes="me-1" />
{I18NextService.i18n.t("edit")}
</button>
<button
className="d-inline-block btn btn-outline-danger"
onClick={onDelete}
>
<Icon icon="trash" classes="me-1" />
{I18NextService.i18n.t("delete")}
</button>
</div>
</summary>
<div className="container">
<dl className="row row-cols-1 row-cols-sm-2 row-cols-md-3">
<TextInfoField i18nKey="oauth_issuer" data={provider.issuer} />
<TextInfoField
i18nKey="oauth_authorization_endpoint"
data={provider.authorization_endpoint}
/>
<TextInfoField
i18nKey="oauth_token_endpoint"
data={provider.token_endpoint}
/>
<TextInfoField
i18nKey="oauth_userinfo_endpoint"
data={provider.userinfo_endpoint}
/>
<TextInfoField i18nKey="oauth_id_claim" data={provider.id_claim} />
<TextInfoField
i18nKey="oauth_client_id"
data={provider.client_id}
/>
<TextInfoField i18nKey="oauth_scopes" data={provider.scopes} />
<TextInfoField
i18nKey="oauth_auto_verify_email"
data={boolToYesNo(provider.auto_verify_email)}
/>
<TextInfoField
i18nKey="oauth_account_linking_enabled"
data={boolToYesNo(provider.account_linking_enabled)}
/>
<TextInfoField
i18nKey="oauth_enabled"
data={boolToYesNo(provider.enabled)}
/>
</dl>
</div>
</details>
</li>
);
}

View file

@ -0,0 +1,204 @@
import { Component, linkEvent } from "inferno";
import { I18NextService } from "../../../services/I18NextService";
import {
CreateOAuthProvider,
DeleteOAuthProvider,
EditOAuthProvider,
OAuthProvider,
} from "lemmy-js-client";
import OAuthProviderListItem from "./oauth-provider-list-item";
import CreateOrEditOAuthProviderModal, {
CreateOrEditOAuthProviderModalData,
} from "../../common/modal/create-or-edit-oauth-provider-modal";
import ConfirmationModal from "../../common/modal/confirmation-modal";
import { ProviderToEdit } from "@utils/types/oauth";
type OAuthProvidersTabProps = {
oauthProviders: OAuthProvider[];
onEdit(form: EditOAuthProvider): Promise<void>;
onCreate(form: CreateOAuthProvider): Promise<void>;
onDelete(form: DeleteOAuthProvider): Promise<void>;
};
type OAuthProvidersTabState = {
providerToDelete?: OAuthProvider;
createOrEditModalData?: CreateOrEditOAuthProviderModalData;
};
const PRESET_OAUTH_PROVIDERS: ProviderToEdit[] = [
{
display_name: "Privacy Portal",
issuer: "https://api.privacyportal.org/",
authorization_endpoint: "https://app.privacyportal.org/oauth/authorize",
token_endpoint: "https://api.privacyportal.org/oauth/token",
userinfo_endpoint: "https://api.privacyportal.org/oauth/userinfo",
id_claim: "sub",
scopes: "openid email",
auto_verify_email: true,
account_linking_enabled: true,
enabled: true,
},
// additional preset providers can be added here
];
function handleShowCreateOrEditProviderModal({
data,
tab,
}: {
tab: OAuthProvidersTab;
data: CreateOrEditOAuthProviderModalData;
}) {
tab.setState({
createOrEditModalData: data,
});
}
function handleCloseCreateOrEditModal(tab: OAuthProvidersTab) {
tab.setState({
createOrEditModalData: undefined,
});
}
function handleTryDeleteOauthProvider({
tab,
provider,
}: {
tab: OAuthProvidersTab;
provider: OAuthProvider;
}) {
tab.setState({ providerToDelete: provider });
}
function handleCloseDeleteConfirmationModal(tab: OAuthProvidersTab) {
tab.setState({ providerToDelete: undefined });
}
export default class OAuthProvidersTab extends Component<
OAuthProvidersTabProps,
OAuthProvidersTabState
> {
state: OAuthProvidersTabState = {};
constructor(props: OAuthProvidersTabProps, context: any) {
super(props, context);
this.handleDeleteProvider = this.handleDeleteProvider.bind(this);
this.handleCreateOrEditProviderSubmit =
this.handleCreateOrEditProviderSubmit.bind(this);
}
render(
{ oauthProviders }: Readonly<OAuthProvidersTabProps>,
{
providerToDelete,
createOrEditModalData,
}: Readonly<OAuthProvidersTabState>,
) {
return (
<div className="oauth-providers-tab">
<h1 className="h4 mb-4">{I18NextService.i18n.t("oauth_config")}</h1>
{oauthProviders.length > 0 ? (
<>
<h2 className="h5 mb-2">
{I18NextService.i18n.t("oauth_providers")}
</h2>
<ul className="list-group">
{oauthProviders.map(provider => (
<OAuthProviderListItem
provider={provider}
key={provider.id}
onEdit={linkEvent(
{ data: { type: "edit", provider }, tab: this },
handleShowCreateOrEditProviderModal,
)}
onDelete={linkEvent(
{ provider, tab: this },
handleTryDeleteOauthProvider,
)}
/>
))}
</ul>
</>
) : (
<div>{I18NextService.i18n.t("no_oauth_providers_blurb")}</div>
)}
<button
type="button"
className="btn btn-secondary btn-small mt-3"
onClick={linkEvent(
{ data: { type: "add" }, tab: this },
handleShowCreateOrEditProviderModal,
)}
>
{I18NextService.i18n.t("add_oauth_provider")}
</button>
{PRESET_OAUTH_PROVIDERS.length > 0 && (
<section className="default-oauth-providers-section mt-4">
<h2 className="h5 mb-3">
{I18NextService.i18n.t("oauth_provider_presets")}
</h2>
<ul className="d-flex flex-wrap gap-3 ps-0">
{PRESET_OAUTH_PROVIDERS.map(provider => {
const isAlreadyUsed = oauthProviders.some(
p => p.issuer === provider.issuer,
);
return (
<li key={provider.issuer}>
<button
className="btn btn-secondary btn-small"
disabled={isAlreadyUsed}
onClick={linkEvent(
{ data: { type: "add", provider }, tab: this },
handleShowCreateOrEditProviderModal,
)}
>
{provider.display_name}
</button>
</li>
);
})}
</ul>
</section>
)}
<CreateOrEditOAuthProviderModal
show={!!createOrEditModalData}
onClose={linkEvent(this, handleCloseCreateOrEditModal)}
onSubmit={this.handleCreateOrEditProviderSubmit}
data={createOrEditModalData ?? { type: "add" }}
/>
<ConfirmationModal
show={!!providerToDelete}
message={I18NextService.i18n.t("delete_oauth_provider_are_you_sure")}
loadingMessage={I18NextService.i18n.t("deleting_oauth_provider")}
onNo={linkEvent(this, handleCloseDeleteConfirmationModal)}
onYes={this.handleDeleteProvider}
/>
</div>
);
}
async handleDeleteProvider() {
const id = this.state.providerToDelete?.id;
if (id !== undefined) {
await this.props.onDelete({ id });
}
this.setState({ providerToDelete: undefined });
}
async handleCreateOrEditProviderSubmit(
provider: CreateOAuthProvider | EditOAuthProvider,
) {
if (this.state.createOrEditModalData?.type === "edit") {
await this.props.onEdit(provider as EditOAuthProvider);
} else {
await this.props.onCreate(provider as CreateOAuthProvider);
}
this.setState({
createOrEditModalData: undefined,
});
}
}

View file

@ -3,7 +3,7 @@ import classNames from "classnames";
import { Component, FormEventHandler, linkEvent } from "inferno";
import { EditSite, LocalSiteRateLimit } from "lemmy-js-client";
import { I18NextService } from "../../services";
import { Spinner } from "../common/icon";
import { Icon, Spinner } from "../common/icon";
import Tabs from "../common/tabs";
const rateLimitTypes = [
@ -144,6 +144,10 @@ export default class RateLimitsForm extends Component<
<h1 className="h4 mb-4">
{I18NextService.i18n.t("rate_limit_header")}
</h1>
<div className="alert small alert-info" role="alert">
<Icon icon="info" classes="icon-inline me-2" />
{I18NextService.i18n.t("rate_limit_info")}
</div>
<Tabs
tabs={rateLimitTypes.map(rateLimitType => ({
key: rateLimitType,

View file

@ -19,6 +19,7 @@ import PasswordInput from "../common/password-input";
import { SiteForm } from "./site-form";
import { simpleScrollMixin } from "../mixins/scroll-mixin";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { isBrowser } from "@utils/browser";
interface State {
form: {
@ -61,8 +62,10 @@ export class Setup extends Component<
this.handleCreateSite = this.handleCreateSite.bind(this);
}
async componentDidMount() {
this.setState({ themeList: await fetchThemeList() });
async componentWillMount() {
if (isBrowser()) {
this.setState({ themeList: await fetchThemeList() });
}
}
get documentTitle(): string {

View file

@ -1,6 +1,6 @@
import { setIsoData } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { resourcesSettled, validEmail } from "@utils/helpers";
import { getQueryParams, resourcesSettled, validEmail } from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { Component, linkEvent } from "inferno";
import { T } from "inferno-i18next-dess";
@ -11,7 +11,7 @@ import {
LoginResponse,
SiteView,
} from "lemmy-js-client";
import { joinLemmyUrl } from "../../config";
import { joinLemmyUrl, validActorRegexPattern } from "../../config";
import { mdToHtml } from "../../markdown";
import { I18NextService, UserService } from "../../services";
import {
@ -26,6 +26,13 @@ import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
import PasswordInput from "../common/password-input";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { RouteData } from "../../interfaces";
import { IRoutePropsWithFetch } from "../../routes";
import { handleUseOAuthProvider } from "./login";
interface SignupProps {
sso_provider_id?: string;
}
interface State {
registerRes: RequestState<LoginResponse>;
@ -45,11 +52,25 @@ interface State {
siteRes: GetSiteResponse;
}
export function getSignupQueryParams(source?: string): SignupProps {
return getQueryParams<SignupProps>(
{
sso_provider_id: (param?: string) => param,
},
source,
);
}
type SignupRouteProps = RouteComponentProps<Record<string, never>> &
SignupProps;
export type SignupFetchConfig = IRoutePropsWithFetch<
RouteData,
Record<string, never>,
SignupProps
>;
@scrollMixin
export class Signup extends Component<
RouteComponentProps<Record<string, never>>,
State
> {
export class Signup extends Component<SignupRouteProps, State> {
private isoData = setIsoData(this.context);
private audio?: HTMLAudioElement;
@ -76,8 +97,11 @@ export class Signup extends Component<
this.handleAnswerChange = this.handleAnswerChange.bind(this);
}
async componentDidMount() {
if (this.state.siteRes.site_view.local_site.captcha_enabled) {
async componentWillMount() {
if (
this.state.siteRes.site_view.local_site.captcha_enabled &&
isBrowser()
) {
await this.fetchCaptcha();
}
}
@ -129,6 +153,8 @@ export class Signup extends Component<
registerForm() {
const siteView = this.state.siteRes.site_view;
const oauth_provider = getOAuthProvider(this);
return (
<form
className="was-validated"
@ -163,63 +189,85 @@ export class Signup extends Component<
onInput={linkEvent(this, this.handleRegisterUsernameChange)}
required
minLength={3}
pattern="[a-zA-Z0-9_]+"
pattern={validActorRegexPattern}
title={I18NextService.i18n.t("community_reqs")}
/>
</div>
</div>
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="register-email">
{I18NextService.i18n.t("email")}
</label>
<div className="col-sm-10">
<input
type="email"
id="register-email"
className="form-control"
placeholder={
siteView.local_site.require_email_verification
? I18NextService.i18n.t("required")
: I18NextService.i18n.t("optional")
}
value={this.state.form.email}
autoComplete="email"
onInput={linkEvent(this, this.handleRegisterEmailChange)}
required={siteView.local_site.require_email_verification}
minLength={3}
/>
{!siteView.local_site.require_email_verification &&
this.state.form.email &&
!validEmail(this.state.form.email) && (
<div className="mt-2 mb-0 alert alert-warning" role="alert">
<Icon icon="alert-triangle" classes="icon-inline me-2" />
{I18NextService.i18n.t("no_password_reset")}
{!oauth_provider && (
<>
{
<div className="mb-3 row">
<label
className="col-sm-2 col-form-label"
htmlFor="register-email"
>
{I18NextService.i18n.t("email")}
</label>
<div className="col-sm-10">
<input
type="email"
id="register-email"
className="form-control"
placeholder={
siteView.local_site.require_email_verification
? I18NextService.i18n.t("required")
: I18NextService.i18n.t("optional")
}
value={this.state.form.email}
autoComplete="email"
onInput={linkEvent(this, this.handleRegisterEmailChange)}
required={siteView.local_site.require_email_verification}
minLength={3}
/>
{!siteView.local_site.require_email_verification &&
this.state.form.email &&
!validEmail(this.state.form.email) && (
<div
className="mt-2 mb-0 alert alert-warning"
role="alert"
>
<Icon
icon="alert-triangle"
classes="icon-inline me-2"
/>
{I18NextService.i18n.t("no_password_reset")}
</div>
)}
</div>
)}
</div>
</div>
</div>
}
<div className="mb-3">
<PasswordInput
id="register-password"
value={this.state.form.password}
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
showStrength
label={I18NextService.i18n.t("password")}
isNew
/>
</div>
{
<div className="mb-3">
<PasswordInput
id="register-password"
value={this.state.form.password}
onInput={linkEvent(this, this.handleRegisterPasswordChange)}
showStrength
label={I18NextService.i18n.t("password")}
isNew
/>
</div>
}
<div className="mb-3">
<PasswordInput
id="register-verify-password"
value={this.state.form.password_verify}
onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)}
label={I18NextService.i18n.t("verify_password")}
isNew
/>
</div>
{
<div className="mb-3">
<PasswordInput
id="register-verify-password"
value={this.state.form.password_verify}
onInput={linkEvent(
this,
this.handleRegisterPasswordVerifyChange,
)}
label={I18NextService.i18n.t("verify_password")}
isNew
/>
</div>
}
</>
)}
{siteView.local_site.registration_mode === "RequireApplication" && (
<>
@ -293,7 +341,12 @@ export class Signup extends Component<
{this.state.registerRes.state === "loading" ? (
<Spinner />
) : (
this.titleName(siteView)
[
this.titleName(siteView),
...(oauth_provider
? [`(${oauth_provider.display_name})`]
: []),
].join(" ")
)}
</button>
</div>
@ -386,6 +439,19 @@ export class Signup extends Component<
password_verify,
username,
} = i.state.form;
const oauthProvider = getOAuthProvider(i);
// oauth registration
if (username && oauthProvider)
return handleUseOAuthProvider({
oauth_provider: oauthProvider,
username,
answer,
show_nsfw,
});
// normal registration
if (username && password && password_verify) {
i.setState({ registerRes: LOADING_REQUEST });
@ -513,3 +579,9 @@ export class Signup extends Component<
return `data:image/png;base64,${captcha.png}`;
}
}
function getOAuthProvider(signup: Signup) {
return (signup.state.siteRes.oauth_providers ?? []).find(
provider => provider.id === Number(signup.props?.sso_provider_id ?? -1),
);
}

View file

@ -2,7 +2,6 @@ import { capitalizeFirstLetter, validInstanceTLD } from "@utils/helpers";
import {
Component,
InfernoKeyboardEvent,
InfernoMouseEvent,
InfernoNode,
linkEvent,
} from "inferno";
@ -64,7 +63,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
description: site.description,
enable_downvotes: ls.enable_downvotes,
registration_mode: ls.registration_mode,
enable_nsfw: ls.enable_nsfw,
oauth_registration: ls.oauth_registration,
community_creation_admin_only: ls.community_creation_admin_only,
icon: site.icon,
banner: site.banner,
@ -334,6 +333,25 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
</div>
</div>
)}
<div className="mb-3 row">
<div className="col-12">
<div className="form-check">
<input
className="form-check-input"
id="create-site-oauth-registration"
type="checkbox"
checked={this.state.siteForm.oauth_registration}
onChange={linkEvent(this, this.handleSiteOauthRegistration)}
/>
<label
className="form-check-label"
htmlFor="create-site-oauth-registration"
>
{I18NextService.i18n.t("oauth_registration")}
</label>
</div>
</div>
</div>
<div className="mb-3 row">
<div className="col-12">
<div className="form-check">
@ -783,6 +801,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
enable_downvotes: stateSiteForm.enable_downvotes,
application_question: stateSiteForm.application_question,
registration_mode: stateSiteForm.registration_mode,
oauth_registration: stateSiteForm.oauth_registration,
require_email_verification: stateSiteForm.require_email_verification,
private_instance: stateSiteForm.private_instance,
default_theme: stateSiteForm.default_theme,
@ -878,42 +897,6 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
this.setState(s => ((s.siteForm.legal_information = val), s));
}
handleTaglineChange(i: SiteForm, index: number, val: string) {
const taglines = i.state.siteForm.taglines;
if (taglines) {
taglines[index] = val;
i.setState(i.state);
}
}
handleDeleteTaglineClick(
i: SiteForm,
index: number,
event: InfernoMouseEvent<HTMLButtonElement>,
) {
event.preventDefault();
const taglines = i.state.siteForm.taglines;
if (taglines) {
taglines.splice(index, 1);
i.state.siteForm.taglines = undefined;
i.setState(i.state);
i.state.siteForm.taglines = taglines;
i.setState(i.state);
}
}
handleAddTaglineClick(
i: SiteForm,
event: InfernoMouseEvent<HTMLButtonElement>,
) {
event.preventDefault();
if (!i.state.siteForm.taglines) {
i.state.siteForm.taglines = [];
}
i.state.siteForm.taglines.push("");
i.setState(i.state);
}
handleSiteApplicationQuestionChange(val: string) {
this.setState(s => ((s.siteForm.application_question = val), s));
}
@ -933,6 +916,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
i.setState(i.state);
}
handleSiteOauthRegistration(i: SiteForm, event: any) {
i.state.siteForm.oauth_registration = event.target.checked;
i.setState(i.state);
}
handleSiteCommunityCreationAdminOnly(i: SiteForm, event: any) {
i.state.siteForm.community_creation_admin_only = event.target.checked;
i.setState(i.state);

View file

@ -1,49 +1,84 @@
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoMouseEvent, linkEvent } from "inferno";
import { EditSite, Tagline } from "lemmy-js-client";
import { I18NextService } from "../../services";
import { Tagline } from "lemmy-js-client";
import { HttpService, I18NextService } from "../../services";
import { Icon, Spinner } from "../common/icon";
import { MarkdownTextArea } from "../common/markdown-textarea";
import { tippyMixin } from "../mixins/tippy-mixin";
import { Paginator } from "../common/paginator";
import classNames from "classnames";
import { isBrowser } from "@utils/browser";
import { Prompt } from "inferno-router";
interface TaglineFormProps {
taglines: Array<Tagline>;
onSaveSite(form: EditSite): void;
loading: boolean;
interface EditableTagline {
change?: "update" | "delete" | "create";
editMode?: boolean;
tagline: Tagline;
}
function markForUpdate(editable: EditableTagline) {
if (editable.change !== "create") {
editable.change = "update";
}
}
interface TaglineFormState {
taglines: Array<string>;
editingRow?: number;
taglines: Array<EditableTagline>;
page: number;
loading: boolean;
}
@tippyMixin
export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
export class TaglineForm extends Component<
Record<never, never>,
TaglineFormState
> {
state: TaglineFormState = {
editingRow: undefined,
taglines: this.props.taglines.map(x => x.content),
taglines: [],
page: 1,
loading: false,
};
constructor(props: any, context: any) {
super(props, context);
this.handlePageChange = this.handlePageChange.bind(this);
}
componentWillMount(): void {
if (isBrowser()) {
this.handlePageChange(1);
}
}
hasPendingChanges(): boolean {
return this.state.taglines.some(x => x.change);
}
render() {
return (
<div className="tagline-form col-12">
<Prompt
message={I18NextService.i18n.t("block_leaving")}
when={this.hasPendingChanges()}
/>
<h1 className="h4 mb-4">{I18NextService.i18n.t("taglines")}</h1>
<div className="table-responsive col-12">
<table id="taglines_table" className="table table-sm table-hover">
<table
id="taglines_table"
className="table table-sm table-hover align-middle"
>
<thead className="pointer">
<th></th>
<th style="width:60px"></th>
<th style="width:121px"></th>
</thead>
<tbody>
{this.state.taglines.map((cv, index) => (
<tr key={index}>
<td>
{this.state.editingRow === index && (
{cv.editMode ? (
<MarkdownTextArea
initialContent={cv}
initialContent={cv.tagline.content}
focus={true}
onContentChange={s =>
this.handleTaglineChange(this, index, s)
}
@ -51,8 +86,32 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
allLanguages={[]}
siteLanguages={[]}
/>
) : (
<div>{cv.tagline.content}</div>
)}
</td>
<td
className={classNames("text-center", {
"border-info": cv.change === "update",
"border-danger": cv.change === "delete",
"border-warning": cv.change === "create",
})}
>
{cv.change === "update" && (
<span>
<Icon icon="transfer" />
</span>
)}
{cv.change === "delete" && (
<span>
<Icon icon="trash" />
</span>
)}
{cv.change === "create" && (
<span>
<Icon icon="add" inline />
</span>
)}
{this.state.editingRow !== index && <div>{cv}</div>}
</td>
<td className="text-right">
<button
@ -99,65 +158,153 @@ export class TaglineForm extends Component<TaglineFormProps, TaglineFormState> {
<button
onClick={linkEvent(this, this.handleSaveClick)}
className="btn btn-secondary me-2"
disabled={this.props.loading}
disabled={this.state.loading || !this.hasPendingChanges()}
>
{this.props.loading ? (
{this.state.loading ? (
<Spinner />
) : (
capitalizeFirstLetter(I18NextService.i18n.t("save"))
)}
</button>
{this.hasPendingChanges() && (
<button
onClick={linkEvent(this, this.handleCancelClick)}
className="btn btn-secondary me-2"
>
{I18NextService.i18n.t("cancel")}
</button>
)}
</div>
</div>
<div>
<Paginator
page={this.state.page}
onChange={this.handlePageChange}
nextDisabled={false}
disabled={this.hasPendingChanges()}
/>
</div>
</div>
</div>
);
}
handleTaglineChange(i: TaglineForm, index: number, val: string) {
if (i.state.taglines) {
i.setState(prev => ({
...prev,
taglines: prev.taglines.map((tl, i) => (i === index ? val : tl)),
}));
}
const editable = i.state.taglines[index];
i.setState(() => {
markForUpdate(editable);
const tagline: Tagline = editable.tagline;
tagline.content = val;
});
}
handleDeleteTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
async handleDeleteTaglineClick(
d: { i: TaglineForm; index: number },
event: any,
) {
event.preventDefault();
d.i.setState(prev => ({
...prev,
taglines: prev.taglines.filter((_, i) => i !== d.index),
editingRow: undefined,
}));
const editable = d.i.state.taglines[d.index];
if (editable.change === "create") {
// This drops the entry immediately, other deletes have to be saved.
d.i.setState(prev => {
return { taglines: prev.taglines.filter(x => x !== editable) };
});
} else {
d.i.setState(() => {
editable.change = "delete";
editable.editMode = false;
});
}
}
handleEditTaglineClick(d: { i: TaglineForm; index: number }, event: any) {
event.preventDefault();
if (d.i.state.editingRow === d.index) {
d.i.setState({ editingRow: undefined });
} else {
d.i.setState({ editingRow: d.index });
}
}
async handleSaveClick(i: TaglineForm) {
i.props.onSaveSite({
taglines: i.state.taglines,
const editable = d.i.state.taglines[d.index];
d.i.setState(prev => {
prev.taglines
.filter(x => x !== editable)
.forEach(x => {
x.editMode = false;
});
editable.editMode = !editable.editMode;
});
}
handleAddTaglineClick(
async handleSaveClick(i: TaglineForm) {
const promises: Promise<any>[] = [];
for (const editable of i.state.taglines) {
if (editable.change === "update") {
promises.push(
HttpService.client.editTagline(editable.tagline).then(res => {
if (res.state === "success") {
i.setState(() => {
editable.change = undefined;
editable.tagline = res.data.tagline;
});
}
}),
);
} else if (editable.change === "delete") {
promises.push(
HttpService.client.deleteTagline(editable.tagline).then(res => {
if (res.state === "success") {
i.setState(() => {
editable.change = undefined;
return {
taglines: this.state.taglines.filter(x => x !== editable),
};
});
}
}),
);
} else if (editable.change === "create") {
promises.push(
HttpService.client.createTagline(editable.tagline).then(res => {
if (res.state === "success") {
i.setState(() => {
editable.change = undefined;
editable.tagline = res.data.tagline;
});
}
}),
);
}
}
await Promise.all(promises);
}
async handleCancelClick(i: TaglineForm) {
i.handlePageChange(i.state.page);
}
async handleAddTaglineClick(
i: TaglineForm,
event: InfernoMouseEvent<HTMLButtonElement>,
) {
event.preventDefault();
const newTaglines = [...i.state.taglines];
newTaglines.push("");
i.setState({
taglines: newTaglines,
editingRow: newTaglines.length - 1,
i.setState(prev => {
prev.taglines.forEach(x => {
x.editMode = false;
});
prev.taglines.push({
tagline: { id: -1, content: "", published: "" },
change: "create",
editMode: true,
});
});
}
async handlePageChange(val: number) {
this.setState({ loading: true });
const taglineRes = await HttpService.client.listTaglines({ page: val });
if (taglineRes.state === "success") {
this.setState({
page: val,
loading: false,
taglines: taglineRes.data.taglines.map(t => ({ tagline: t })),
});
} else {
this.setState({ loading: false });
}
}
}

View file

@ -2,7 +2,7 @@ import { Modal } from "bootstrap";
import { Component, InfernoNode, RefObject } from "inferno";
export function modalMixin<
P extends { show: boolean },
P extends { show?: boolean },
S,
Base extends new (...args: any[]) => Component<P, S> & {
readonly modalDivRef: RefObject<HTMLDivElement>;

View file

@ -1,6 +1,6 @@
import { isBrowser, nextUserAction, snapToTop } from "../../utils/browser";
import { Component, InfernoNode } from "inferno";
import { Location } from "history";
import { Location, History, Action } from "history";
function restoreScrollPosition(props: { location: Location }) {
const key: string = props.location.key;
@ -25,7 +25,7 @@ function dropScrollPosition(props: { location: Location }) {
}
export function scrollMixin<
P extends { location: Location },
P extends { location: Location; history: History },
S,
Base extends new (
...args: any
@ -68,10 +68,11 @@ export function scrollMixin<
nextProps: Readonly<{ children?: InfernoNode } & P>,
nextContext: any,
) {
// Currently this is hypothetical. Components unmount before route changes.
if (this.props.location.key !== nextProps.location.key) {
this.saveFinalPosition();
this.reset();
if (nextProps.history.action !== Action.Replace) {
this.saveFinalPosition();
this.reset();
}
}
return super.componentWillReceiveProps?.(nextProps, nextContext);
}
@ -131,7 +132,7 @@ export function scrollMixin<
}
export function simpleScrollMixin<
P extends { location: Location },
P extends { location: Location; history: History },
S,
Base extends new (...args: any) => Component<P, S>,
>(base: Base, _context?: ClassDecoratorContext<Base>) {

View file

@ -1,9 +1,4 @@
import {
fetchUsers,
getUpdatedSearchId,
personToChoice,
setIsoData,
} from "@utils/app";
import { fetchUsers, personToChoice, setIsoData } from "@utils/app";
import {
debounce,
formatPastDate,
@ -12,6 +7,7 @@ import {
getQueryParams,
getQueryString,
resourcesSettled,
bareRoutePush,
} from "@utils/helpers";
import { scrollMixin } from "./mixins/scroll-mixin";
import { amAdmin, amMod } from "@utils/roles";
@ -66,6 +62,8 @@ import { CommunityLink } from "./community/community-link";
import { PersonListing } from "./person/person-listing";
import { getHttpBaseInternal } from "../utils/env";
import { IRoutePropsWithFetch } from "../routes";
import { isBrowser } from "@utils/browser";
import { LoadingEllipses } from "./common/loading-ellipses";
type FilterType = "mod" | "user";
@ -703,40 +701,68 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
const { modId, userId } = this.props;
const promises = [this.refetch()];
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await Promise.all([
this.fetchModlog(this.props),
this.fetchCommunity(this.props),
this.fetchUser(this.props),
this.fetchMod(this.props),
]);
}
}
if (userId) {
promises.push(
HttpService.client
.getPersonDetails({ person_id: userId })
.then(res => {
if (res.state === "success") {
this.setState({
userSearchOptions: [personToChoice(res.data.person_view)],
});
}
}),
);
componentWillReceiveProps(nextProps: ModlogRouteProps) {
this.fetchModlog(nextProps);
const reload = bareRoutePush(this.props, nextProps);
if (nextProps.modId !== this.props.modId || reload) {
this.fetchMod(nextProps);
}
if (nextProps.userId !== this.props.userId || reload) {
this.fetchUser(nextProps);
}
if (
nextProps.match.params.communityId !==
this.props.match.params.communityId ||
reload
) {
this.fetchCommunity(nextProps);
}
}
fetchUserToken?: symbol;
async fetchUser(props: ModlogRouteProps) {
const token = (this.fetchUserToken = Symbol());
const { userId } = props;
if (userId) {
const res = await HttpService.client.getPersonDetails({
person_id: userId,
});
if (res.state === "success" && token === this.fetchUserToken) {
this.setState({
userSearchOptions: [personToChoice(res.data.person_view)],
});
}
}
}
if (modId) {
promises.push(
HttpService.client
.getPersonDetails({ person_id: modId })
.then(res => {
if (res.state === "success") {
this.setState({
modSearchOptions: [personToChoice(res.data.person_view)],
});
}
}),
);
fetchModToken?: symbol;
async fetchMod(props: ModlogRouteProps) {
const token = (this.fetchModToken = Symbol());
const { modId } = props;
if (modId) {
const res = await HttpService.client.getPersonDetails({
person_id: modId,
});
if (res.state === "success" && token === this.fetchModToken) {
this.setState({
modSearchOptions: [personToChoice(res.data.person_view)],
});
}
await Promise.all(promises);
}
}
@ -793,6 +819,11 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
modSearchOptions,
} = this.state;
const { actionType, modId, userId } = this.props;
const { communityId } = this.props.match.params;
const communityState = this.state.communityRes.state;
const communityResp =
communityState === "success" && this.state.communityRes.data;
return (
<div className="modlog container-lg">
@ -816,15 +847,26 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
#<strong>#</strong>#
</T>
</div>
{this.state.communityRes.state === "success" && (
{communityId && (
<h5>
<Link
className="text-body"
to={`/c/${this.state.communityRes.data.community_view.community.name}`}
>
/c/{this.state.communityRes.data.community_view.community.name}{" "}
</Link>
<span>{I18NextService.i18n.t("modlog")}</span>
{communityResp ? (
<>
<Link
className="text-body"
to={`/c/${communityResp.community_view.community.name}`}
>
/c/{communityResp.community_view.community.name}
</Link>{" "}
<span>{I18NextService.i18n.t("modlog")}</span>
</>
) : (
communityState === "loading" && (
<>
<LoadingEllipses />
&nbsp;
</>
)
)}
</h5>
)}
<div className="row mb-2">
@ -865,8 +907,9 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
options={userSearchOptions}
loading={loadingUserSearch}
/>
{!this.isoData.site_res.site_view.local_site
.hide_modlog_mod_names && (
{(this.amAdminOrMod ||
!this.isoData.site_res.site_view.local_site
.hide_modlog_mod_names) && (
<Filter
filterType="mod"
onChange={this.handleModChange}
@ -935,6 +978,10 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
}
handleSearchUsers = debounce(async (text: string) => {
if (!text.length) {
return;
}
const { userId } = this.props;
const { userSearchOptions } = this.state;
this.setState({ loadingUserSearch: true });
@ -952,6 +999,10 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
});
handleSearchMods = debounce(async (text: string) => {
if (!text.length) {
return;
}
const { modId } = this.props;
const { modSearchOptions } = this.state;
this.setState({ loadingModSearch: true });
@ -968,61 +1019,73 @@ export class Modlog extends Component<ModlogRouteProps, ModlogState> {
});
});
async updateUrl({ actionType, modId, page, userId }: Partial<ModlogProps>) {
async updateUrl(props: Partial<ModlogProps>) {
const {
page: urlPage,
actionType: urlActionType,
modId: urlModId,
userId: urlUserId,
} = this.props;
actionType,
modId,
page,
userId,
match: {
params: { communityId },
},
} = { ...this.props, ...props };
const queryParams: QueryParams<ModlogProps> = {
page: (page ?? urlPage).toString(),
actionType: actionType ?? urlActionType,
modId: getUpdatedSearchId(modId, urlModId),
userId: getUpdatedSearchId(userId, urlUserId),
page: page.toString(),
actionType: actionType,
modId: modId?.toString(),
userId: userId?.toString(),
};
const communityId = this.props.match.params.communityId;
this.props.history.push(
`/modlog${communityId ? `/${communityId}` : ""}${getQueryString(
queryParams,
)}`,
);
await this.refetch();
}
async refetch() {
const { actionType, page, modId, userId, postId, commentId } = this.props;
const { communityId: urlCommunityId } = this.props.match.params;
fetchModlogToken?: symbol;
async fetchModlog(props: ModlogRouteProps) {
const token = (this.fetchModlogToken = Symbol());
const { actionType, page, modId, userId, postId, commentId } = props;
const { communityId: urlCommunityId } = props.match.params;
const communityId = getIdFromString(urlCommunityId);
this.setState({ res: LOADING_REQUEST });
this.setState({
res: await HttpService.client.getModlog({
community_id: communityId,
page,
limit: fetchLimit,
type_: actionType,
other_person_id: userId,
mod_person_id: !this.isoData.site_res.site_view.local_site
.hide_modlog_mod_names
? modId
: undefined,
comment_id: commentId,
post_id: postId,
}),
const res = await HttpService.client.getModlog({
community_id: communityId,
page,
limit: fetchLimit,
type_: actionType,
other_person_id: userId,
mod_person_id: !this.isoData.site_res.site_view.local_site
.hide_modlog_mod_names
? modId
: undefined,
comment_id: commentId,
post_id: postId,
});
if (token === this.fetchModlogToken) {
this.setState({ res });
}
}
fetchCommunityToken?: symbol;
async fetchCommunity(props: ModlogRouteProps) {
const token = (this.fetchCommunityToken = Symbol());
const { communityId: urlCommunityId } = props.match.params;
const communityId = getIdFromString(urlCommunityId);
if (communityId) {
this.setState({ communityRes: LOADING_REQUEST });
this.setState({
communityRes: await HttpService.client.getCommunity({
id: communityId,
}),
const communityRes = await HttpService.client.getCommunity({
id: communityId,
});
if (token === this.fetchCommunityToken) {
this.setState({ communityRes });
}
} else {
this.setState({ communityRes: EMPTY_REQUEST });
}
}

View file

@ -5,7 +5,6 @@ import {
editPrivateMessage,
editWith,
enableDownvotes,
getCommentParentId,
myAuth,
setIsoData,
updatePersonBlock,
@ -28,7 +27,6 @@ import {
BanPerson,
BanPersonResponse,
BlockPerson,
CommentId,
CommentReplyResponse,
CommentReplyView,
CommentReportResponse,
@ -89,6 +87,7 @@ import { getHttpBaseInternal } from "../../utils/env";
import { CommentsLoadingSkeleton } from "../common/loading-skeleton";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { isBrowser } from "@utils/browser";
enum UnreadOrAll {
Unread,
@ -131,7 +130,6 @@ interface InboxState {
sort: CommentSortType;
page: number;
siteRes: GetSiteResponse;
finished: Map<CommentId, boolean | undefined>;
isIsomorphic: boolean;
}
@ -156,7 +154,6 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
mentionsRes: EMPTY_REQUEST,
messagesRes: EMPTY_REQUEST,
markAllAsReadRes: EMPTY_REQUEST,
finished: new Map(),
isIsomorphic: false,
};
@ -213,8 +210,8 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.refetch();
}
}
@ -511,7 +508,6 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
{ comment_view: i.view as CommentView, children: [], depth: 0 },
]}
viewType={CommentViewType.Flat}
finished={this.state.finished}
markable
showCommunity
showContext
@ -550,7 +546,6 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
depth: 0,
},
]}
finished={this.state.finished}
viewType={CommentViewType.Flat}
markable
showCommunity
@ -622,7 +617,6 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
<CommentNodes
nodes={commentsToFlatNodes(replies)}
viewType={CommentViewType.Flat}
finished={this.state.finished}
markable
showCommunity
showContext
@ -669,7 +663,6 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
key={umv.person_mention.id}
nodes={[{ comment_view: umv, children: [], depth: 0 }]}
viewType={CommentViewType.Flat}
finished={this.state.finished}
markable
showCommunity
showContext
@ -784,40 +777,60 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
return inboxData;
}
refetchToken?: symbol;
async refetch() {
const token = (this.refetchToken = Symbol());
const sort = this.state.sort;
const unread_only = this.state.unreadOrAll === UnreadOrAll.Unread;
const page = this.state.page;
const limit = fetchLimit;
this.setState({ repliesRes: LOADING_REQUEST });
this.setState({
repliesRes: await HttpService.client.getReplies({
repliesRes: LOADING_REQUEST,
mentionsRes: LOADING_REQUEST,
messagesRes: LOADING_REQUEST,
});
const repliesPromise = HttpService.client
.getReplies({
sort,
unread_only,
page,
limit,
}),
});
})
.then(repliesRes => {
if (token === this.refetchToken) {
this.setState({
repliesRes,
});
}
});
this.setState({ mentionsRes: LOADING_REQUEST });
this.setState({
mentionsRes: await HttpService.client.getPersonMentions({
const mentionsPromise = HttpService.client
.getPersonMentions({
sort,
unread_only,
page,
limit,
}),
});
})
.then(mentionsRes => {
if (token === this.refetchToken) {
this.setState({ mentionsRes });
}
});
this.setState({ messagesRes: LOADING_REQUEST });
this.setState({
messagesRes: await HttpService.client.getPrivateMessages({
const messagesPromise = HttpService.client
.getPrivateMessages({
unread_only,
page,
limit,
}),
});
})
.then(messagesRes => {
if (token === this.refetchToken) {
this.setState({ messagesRes });
}
});
await Promise.all([repliesPromise, mentionsPromise, messagesPromise]);
UnreadCounterService.Instance.updateInboxCounts();
}
@ -975,9 +988,13 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
this.findAndUpdateMessage(res);
}
async handleEditMessage(form: EditPrivateMessage) {
async handleEditMessage(form: EditPrivateMessage): Promise<boolean> {
const res = await HttpService.client.editPrivateMessage(form);
this.findAndUpdateMessage(res);
if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
return res.state !== "failed";
}
async handleMarkMessageAsRead(form: MarkPrivateMessageAsRead) {
@ -994,7 +1011,7 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
this.reportToast(res);
}
async handleCreateMessage(form: CreatePrivateMessage) {
async handleCreateMessage(form: CreatePrivateMessage): Promise<boolean> {
const res = await HttpService.client.createPrivateMessage(form);
this.setState(s => {
if (s.messagesRes.state === "success" && res.state === "success") {
@ -1005,6 +1022,10 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
return s;
});
if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
return res.state !== "failed";
}
findAndUpdateMessage(res: RequestState<PrivateMessageResponse>) {
@ -1073,6 +1094,8 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
) {
if (res.state === "success") {
toast(I18NextService.i18n.t("report_created"));
} else if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
}
@ -1092,11 +1115,6 @@ export class Inbox extends Component<InboxRouteProps, InboxState> {
s.mentionsRes.data.mentions,
);
}
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true,
);
return s;
});
}

View file

@ -6,7 +6,6 @@ import {
BanFromCommunity,
BanPerson,
BlockPerson,
CommentId,
CommentResponse,
CommentView,
CreateComment,
@ -38,7 +37,7 @@ import {
RemovePost,
SaveComment,
SavePost,
SortType,
PostSortType,
TransferCommunity,
} from "lemmy-js-client";
import { CommentViewType, PersonDetailsView } from "../../interfaces";
@ -49,13 +48,12 @@ import { RequestState } from "../../services/HttpService";
interface PersonDetailsProps {
personRes: GetPersonDetailsResponse;
finished: Map<CommentId, boolean | undefined>;
admins: PersonView[];
allLanguages: Language[];
siteLanguages: number[];
page: number;
limit: number;
sort: SortType;
sort: PostSortType;
enableDownvotes: boolean;
voteDisplayMode: LocalUserVoteDisplayMode;
enableNsfw: boolean;
@ -153,7 +151,6 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
key={i.id}
nodes={[{ comment_view: c, children: [], depth: 0 }]}
viewType={CommentViewType.Flat}
finished={this.props.finished}
admins={this.props.admins}
noBorder
showCommunity
@ -266,7 +263,6 @@ export class PersonDetails extends Component<PersonDetailsProps, any> {
nodes={commentsToFlatNodes(this.props.personRes.comments)}
viewType={CommentViewType.Flat}
admins={this.props.admins}
finished={this.props.finished}
isTopLevel
showCommunity
showContext

View file

@ -29,7 +29,9 @@ export class PersonListing extends Component<PersonListingProps, any> {
let link: string;
let serverStr: string | undefined = undefined;
const name = useApubName ? person.name : person.display_name ?? person.name;
const name = useApubName
? person.name
: (person.display_name ?? person.name);
if (local) {
link = `/u/${person.name}`;

View file

@ -4,7 +4,6 @@ import {
editWith,
enableDownvotes,
enableNsfw,
getCommentParentId,
setIsoData,
updatePersonBlock,
voteDisplayMode,
@ -19,12 +18,13 @@ import {
numToSI,
randomStr,
resourcesSettled,
bareRoutePush,
} from "@utils/helpers";
import { canMod } from "@utils/roles";
import { amAdmin, canMod } from "@utils/roles";
import type { QueryParams } from "@utils/types";
import { RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { format, parseISO } from "date-fns";
import { format } from "date-fns";
import { NoOptionI18nKeys } from "i18next";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
@ -37,7 +37,6 @@ import {
BanPerson,
BanPersonResponse,
BlockPerson,
CommentId,
CommentReplyResponse,
CommentResponse,
Community,
@ -71,9 +70,10 @@ import {
RemovePost,
SaveComment,
SavePost,
SortType,
PostSortType,
SuccessResponse,
TransferCommunity,
RegistrationApplicationResponse,
} from "lemmy-js-client";
import { fetchLimit, relTags } from "../../config";
import { InitialFetchRequest, PersonDetailsView } from "../../interfaces";
@ -99,6 +99,9 @@ import { PersonListing } from "./person-listing";
import { getHttpBaseInternal } from "../../utils/env";
import { IRoutePropsWithFetch } from "../../routes";
import { MediaUploads } from "../common/media-uploads";
import { cakeDate } from "@utils/helpers";
import { isBrowser } from "@utils/browser";
import DisplayModal from "../common/modal/display-modal";
type ProfileData = RouteDataResponse<{
personRes: GetPersonDetailsResponse;
@ -107,20 +110,24 @@ type ProfileData = RouteDataResponse<{
interface ProfileState {
personRes: RequestState<GetPersonDetailsResponse>;
// personRes and personDetailsRes point to `===` identical data. This allows
// to render the start of the profile while the new details are loading.
personDetailsRes: RequestState<GetPersonDetailsResponse>;
uploadsRes: RequestState<ListMediaResponse>;
registrationRes: RequestState<RegistrationApplicationResponse>;
personBlocked: boolean;
banReason?: string;
banExpireDays?: number;
showBanDialog: boolean;
removeData: boolean;
removeOrRestoreData: boolean;
siteRes: GetSiteResponse;
finished: Map<CommentId, boolean | undefined>;
isIsomorphic: boolean;
showRegistrationDialog: boolean;
}
interface ProfileProps {
view: PersonDetailsView;
sort: SortType;
sort: PostSortType;
page: number;
}
@ -135,13 +142,13 @@ export function getProfileQueryParams(source?: string): ProfileProps {
);
}
function getSortTypeFromQuery(sort?: string): SortType {
return sort ? (sort as SortType) : "New";
function getSortTypeFromQuery(sort?: string): PostSortType {
return sort ? (sort as PostSortType) : "New";
}
function getViewFromProps(view?: string): PersonDetailsView {
return view
? PersonDetailsView[view] ?? PersonDetailsView.Overview
? (PersonDetailsView[view] ?? PersonDetailsView.Overview)
: PersonDetailsView.Overview;
}
@ -175,7 +182,7 @@ function isPersonBlocked(personRes: RequestState<GetPersonDetailsResponse>) {
return (
(personRes.state === "success" &&
UserService.Instance.myUserInfo?.person_blocks.some(
({ target: { id } }) => id === personRes.data.person_view.person.id,
({ id }) => id === personRes.data.person_view.person.id,
)) ??
false
);
@ -194,17 +201,24 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
private isoData = setIsoData<ProfileData>(this.context);
state: ProfileState = {
personRes: EMPTY_REQUEST,
personDetailsRes: EMPTY_REQUEST,
uploadsRes: EMPTY_REQUEST,
personBlocked: false,
siteRes: this.isoData.site_res,
showBanDialog: false,
removeData: false,
finished: new Map(),
removeOrRestoreData: false,
isIsomorphic: false,
showRegistrationDialog: false,
registrationRes: EMPTY_REQUEST,
};
loadingSettled() {
return resourcesSettled([this.state.personRes]);
return resourcesSettled([
this.state.personRes,
this.props.view === PersonDetailsView.Uploads
? this.state.uploadsRes
: this.state.personDetailsRes,
]);
}
constructor(props: ProfileRouteProps, context: any) {
@ -244,6 +258,8 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
this.handlePurgePost = this.handlePurgePost.bind(this);
this.handleFeaturePost = this.handleFeaturePost.bind(this);
this.handleModBanSubmit = this.handleModBanSubmit.bind(this);
this.handleRegistrationShow = this.handleRegistrationShow.bind(this);
this.handleRegistrationClose = this.handleRegistrationClose.bind(this);
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
@ -252,6 +268,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
this.state = {
...this.state,
personRes,
personDetailsRes: personRes,
uploadsRes,
isIsomorphic: true,
personBlocked: isPersonBlocked(personRes),
@ -259,37 +276,100 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
await this.fetchUserData();
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.fetchUserData(this.props, true);
}
}
async fetchUserData() {
const { page, sort, view } = this.props;
componentWillReceiveProps(nextProps: ProfileRouteProps) {
// Overview, Posts and Comments views can use the same data.
const sharedViewTypes = [nextProps.view, this.props.view].every(
v =>
v === PersonDetailsView.Overview ||
v === PersonDetailsView.Posts ||
v === PersonDetailsView.Comments,
);
const reload = bareRoutePush(this.props, nextProps);
const newUsername =
nextProps.match.params.username !== this.props.match.params.username;
if (
(nextProps.view !== this.props.view && !sharedViewTypes) ||
nextProps.sort !== this.props.sort ||
nextProps.page !== this.props.page ||
newUsername ||
reload
) {
this.fetchUserData(nextProps, reload || newUsername);
}
}
fetchUploadsToken?: symbol;
async fetchUploads(props: ProfileRouteProps) {
if (!this.amCurrentUser) {
return;
}
const token = (this.fetchUploadsToken = Symbol());
const { page } = props;
this.setState({ uploadsRes: LOADING_REQUEST });
const form: ListMedia = {
// userId?
page,
limit: fetchLimit,
};
const uploadsRes = await HttpService.client.listMedia(form);
if (token === this.fetchUploadsToken) {
this.setState({ uploadsRes });
}
}
fetchUserDataToken?: symbol;
async fetchUserData(props: ProfileRouteProps, showBothLoading = false) {
const token = (this.fetchUploadsToken = this.fetchUserDataToken = Symbol());
const { page, sort, view } = props;
if (view === PersonDetailsView.Uploads) {
this.fetchUploads(props);
if (!showBothLoading) {
return;
}
this.setState({
personRes: LOADING_REQUEST,
personDetailsRes: LOADING_REQUEST,
});
} else {
if (showBothLoading) {
this.setState({
personRes: LOADING_REQUEST,
personDetailsRes: LOADING_REQUEST,
uploadsRes: EMPTY_REQUEST,
});
} else {
this.setState({
personDetailsRes: LOADING_REQUEST,
uploadsRes: EMPTY_REQUEST,
});
}
}
this.setState({ personRes: LOADING_REQUEST });
const personRes = await HttpService.client.getPersonDetails({
username: this.props.match.params.username,
username: props.match.params.username,
sort,
saved_only: view === PersonDetailsView.Saved,
page,
limit: fetchLimit,
});
this.setState({
personRes,
personBlocked: isPersonBlocked(personRes),
});
if (view === PersonDetailsView.Uploads) {
this.setState({ uploadsRes: LOADING_REQUEST });
const form: ListMedia = {
page,
limit: fetchLimit,
};
const uploadsRes = await HttpService.client.listMedia(form);
this.setState({ uploadsRes });
if (token === this.fetchUserDataToken) {
this.setState({
personRes,
personDetailsRes: personRes,
personBlocked: isPersonBlocked(personRes),
});
}
}
@ -383,6 +463,10 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
const personRes = this.state.personRes.data;
const { page, sort, view } = this.props;
const personDetailsState = this.state.personDetailsRes.state;
const personDetailsRes =
personDetailsState === "success" && this.state.personDetailsRes.data;
return (
<div className="row">
<div className="col-12 col-md-8">
@ -402,50 +486,58 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
{this.renderUploadsRes()}
<PersonDetails
personRes={personRes}
admins={siteRes.admins}
sort={sort}
page={page}
limit={fetchLimit}
finished={this.state.finished}
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
enableNsfw={enableNsfw(siteRes)}
view={view}
onPageChange={this.handlePageChange}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
// TODO all the forms here
onSaveComment={this.handleSaveComment}
onBlockPerson={this.handleBlockPersonAlt}
onDeleteComment={this.handleDeleteComment}
onRemoveComment={this.handleRemoveComment}
onCommentVote={this.handleCommentVote}
onCommentReport={this.handleCommentReport}
onDistinguishComment={this.handleDistinguishComment}
onAddModToCommunity={this.handleAddModToCommunity}
onAddAdmin={this.handleAddAdmin}
onTransferCommunity={this.handleTransferCommunity}
onPurgeComment={this.handlePurgeComment}
onPurgePerson={this.handlePurgePerson}
onCommentReplyRead={this.handleCommentReplyRead}
onPersonMentionRead={this.handlePersonMentionRead}
onBanPersonFromCommunity={this.handleBanFromCommunity}
onBanPerson={this.handleBanPerson}
onCreateComment={this.handleCreateComment}
onEditComment={this.handleEditComment}
onPostEdit={this.handlePostEdit}
onPostVote={this.handlePostVote}
onPostReport={this.handlePostReport}
onLockPost={this.handleLockPost}
onDeletePost={this.handleDeletePost}
onRemovePost={this.handleRemovePost}
onSavePost={this.handleSavePost}
onPurgePost={this.handlePurgePost}
onFeaturePost={this.handleFeaturePost}
onMarkPostAsRead={() => {}}
/>
{personDetailsState === "loading" &&
this.props.view !== PersonDetailsView.Uploads ? (
<h5>
<Spinner large />
</h5>
) : (
personDetailsRes && (
<PersonDetails
personRes={personDetailsRes}
admins={siteRes.admins}
sort={sort}
page={page}
limit={fetchLimit}
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
enableNsfw={enableNsfw(siteRes)}
view={view}
onPageChange={this.handlePageChange}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
// TODO all the forms here
onSaveComment={this.handleSaveComment}
onBlockPerson={this.handleBlockPersonAlt}
onDeleteComment={this.handleDeleteComment}
onRemoveComment={this.handleRemoveComment}
onCommentVote={this.handleCommentVote}
onCommentReport={this.handleCommentReport}
onDistinguishComment={this.handleDistinguishComment}
onAddModToCommunity={this.handleAddModToCommunity}
onAddAdmin={this.handleAddAdmin}
onTransferCommunity={this.handleTransferCommunity}
onPurgeComment={this.handlePurgeComment}
onPurgePerson={this.handlePurgePerson}
onCommentReplyRead={this.handleCommentReplyRead}
onPersonMentionRead={this.handlePersonMentionRead}
onBanPersonFromCommunity={this.handleBanFromCommunity}
onBanPerson={this.handleBanPerson}
onCreateComment={this.handleCreateComment}
onEditComment={this.handleEditComment}
onPostEdit={this.handlePostEdit}
onPostVote={this.handlePostVote}
onPostReport={this.handlePostReport}
onLockPost={this.handleLockPost}
onDeletePost={this.handleDeletePost}
onRemovePost={this.handleRemovePost}
onSavePost={this.handleSavePost}
onPurgePost={this.handlePurgePost}
onFeaturePost={this.handleFeaturePost}
onMarkPostAsRead={() => {}}
/>
)
)}
</div>
<div className="col-12 col-md-4">
@ -473,7 +565,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
{this.getRadio(PersonDetailsView.Comments)}
{this.getRadio(PersonDetailsView.Posts)}
{this.amCurrentUser && this.getRadio(PersonDetailsView.Saved)}
{this.getRadio(PersonDetailsView.Uploads)}
{this.amCurrentUser && this.getRadio(PersonDetailsView.Uploads)}
</div>
);
}
@ -544,6 +636,8 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
personBlocked,
siteRes: { admins },
showBanDialog,
showRegistrationDialog,
registrationRes,
} = this.state;
return (
@ -587,15 +681,27 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
<div className="flex-grow-1 unselectable pointer mx-2"></div>
{!this.amCurrentUser && UserService.Instance.myUserInfo && (
<>
<a
className={`d-flex align-self-start btn btn-secondary me-2 ${
!pv.person.matrix_user_id && "invisible"
}`}
rel={relTags}
href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
>
{I18NextService.i18n.t("send_secure_message")}
</a>
{amAdmin() && (
<Link
className={
"d-flex align-self-start btn btn-secondary me-2"
}
to={`/modlog?userId=${pv.person.id}`}
>
{I18NextService.i18n.t("user_moderation_history", {
user: pv.person.name,
})}
</Link>
)}
{pv.person.matrix_user_id && (
<a
className={`d-flex align-self-start btn btn-secondary me-2`}
rel={relTags}
href={`https://matrix.to/#/${pv.person.matrix_user_id}`}
>
{I18NextService.i18n.t("send_secure_message")}
</a>
)}
<Link
className={
"d-flex align-self-start btn btn-secondary me-2"
@ -656,6 +762,46 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
{capitalizeFirstLetter(I18NextService.i18n.t("unban"))}
</button>
))}
{amAdmin() && (
<>
<button
className={
"d-flex registration-self-start btn btn-secondary me-2"
}
aria-label={I18NextService.i18n.t("view_registration")}
onClick={this.handleRegistrationShow}
>
{I18NextService.i18n.t("view_registration")}
</button>
{showRegistrationDialog && (
<DisplayModal
onClose={this.handleRegistrationClose}
loadingMessage={I18NextService.i18n.t(
"loading_registration",
)}
title={I18NextService.i18n.t("registration_for_user", {
name: pv.person.display_name ?? pv.person.name,
})}
show={showRegistrationDialog}
loading={registrationRes.state === "loading"}
>
{registrationRes.state === "success" ? (
<article
dangerouslySetInnerHTML={mdToHtml(
registrationRes.data.registration_application
.registration_application.answer,
() => this.forceUpdate(),
)}
/>
) : registrationRes.state === "failed" ? (
I18NextService.i18n.t("fetch_registration_error")
) : (
""
)}
</DisplayModal>
)}
</>
)}
</div>
{pv.person.bio && (
<div className="d-flex align-items-center mb-2">
@ -695,7 +841,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
<Icon icon="cake" />
<span className="ms-2">
{I18NextService.i18n.t("cake_day_title")}{" "}
{format(parseISO(pv.person.published), "PPP")}
{format(cakeDate(pv.person.published), "PPP")}
</span>
</div>
{!UserService.Instance.myUserInfo && (
@ -745,7 +891,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
className="form-check-input"
id="mod-ban-remove-data"
type="checkbox"
checked={this.state.removeData}
checked={this.state.removeOrRestoreData}
onChange={linkEvent(this, this.handleModRemoveDataChange)}
/>
<label
@ -787,26 +933,30 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
);
}
async updateUrl({ page, sort, view }: Partial<ProfileProps>) {
const { page: urlPage, sort: urlSort, view: urlView } = this.props;
async updateUrl(props: Partial<ProfileRouteProps>) {
const {
page,
sort,
view,
match: {
params: { username },
},
} = { ...this.props, ...props };
const queryParams: QueryParams<ProfileProps> = {
page: (page ?? urlPage).toString(),
sort: sort ?? urlSort,
view: view ?? urlView,
page: page?.toString(),
sort,
view,
};
const { username } = this.props.match.params;
this.props.history.push(`/u/${username}${getQueryString(queryParams)}`);
await this.fetchUserData();
}
handlePageChange(page: number) {
this.updateUrl({ page });
}
handleSortChange(sort: SortType) {
handleSortChange(sort: PostSortType) {
this.updateUrl({ sort, page: 1 });
}
@ -830,16 +980,42 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
}
handleModRemoveDataChange(i: Profile, event: any) {
i.setState({ removeData: event.target.checked });
i.setState({ removeOrRestoreData: event.target.checked });
}
handleModBanSubmitCancel(i: Profile) {
i.setState({ showBanDialog: false });
}
handleRegistrationShow() {
if (this.state.registrationRes.state !== "success") {
this.setState({ registrationRes: LOADING_REQUEST });
}
this.setState({ showRegistrationDialog: true });
if (this.state.personDetailsRes.state === "success") {
HttpService.client
.getRegistrationApplication({
person_id: this.state.personDetailsRes.data.person_view.person.id,
})
.then(res => {
this.setState({ registrationRes: res });
if (res.state === "failed") {
toast(I18NextService.i18n.t("fetch_registration_error"), "danger");
}
});
}
}
handleRegistrationClose() {
this.setState({ showRegistrationDialog: false });
}
async handleModBanSubmit(i: Profile, event: any) {
event.preventDefault();
const { removeData, banReason, banExpireDays } = i.state;
const { banReason, banExpireDays } = i.state;
const personRes = i.state.personRes;
@ -849,13 +1025,13 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
// If its an unban, restore all their data
if (!ban) {
i.setState({ removeData: false });
i.setState({ removeOrRestoreData: true });
}
const res = await HttpService.client.banPerson({
person_id: person.id,
ban,
remove_data: removeData,
remove_or_restore_data: i.state.removeOrRestoreData,
reason: banReason,
expires: futureDaysToUnixTime(banExpireDays),
});
@ -1089,7 +1265,6 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
res.data.comment_view,
s.personRes.data.comments,
);
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
@ -1111,11 +1286,6 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
this.setState(s => {
if (s.personRes.state === "success" && res.state === "success") {
s.personRes.data.comments.unshift(res.data.comment_view);
// Set finished for the parent
s.finished.set(
getCommentParentId(res.data.comment_view.comment) ?? 0,
true,
);
}
return s;
});

View file

@ -29,6 +29,7 @@ import { UnreadCounterService } from "../../services";
import { getHttpBaseInternal } from "../../utils/env";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { isBrowser } from "@utils/browser";
enum RegistrationState {
Unread,
@ -92,8 +93,8 @@ export class RegistrationApplications extends Component<
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.refetch();
}
}
@ -108,37 +109,41 @@ export class RegistrationApplications extends Component<
}
renderApps() {
switch (this.state.appsRes.state) {
case "loading":
return (
<h5>
<Spinner large />
</h5>
);
case "success": {
const apps = this.state.appsRes.data.registration_applications;
return (
<div className="row">
<div className="col-12">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
<h1 className="h4 mb-4">
{I18NextService.i18n.t("registration_applications")}
</h1>
{this.selects()}
const appsState = this.state.appsRes.state;
const apps =
appsState === "success" &&
this.state.appsRes.data.registration_applications;
return (
<div className="row">
<div className="col-12">
<HtmlTags
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
<h1 className="h4 mb-4">
{I18NextService.i18n.t("registration_applications")}
</h1>
{this.selects()}
{apps ? (
<>
{this.applicationList(apps)}
<Paginator
page={this.state.page}
onChange={this.handlePageChange}
nextDisabled={fetchLimit > apps.length}
/>
</div>
</div>
);
}
}
</>
) : (
appsState === "loading" && (
<div className="text-center">
<Spinner large />
</div>
)
)}
</div>
</div>
);
}
render() {
@ -263,19 +268,22 @@ export class RegistrationApplications extends Component<
};
}
refetchToken?: symbol;
async refetch() {
const token = (this.refetchToken = Symbol());
const unread_only =
this.state.registrationState === RegistrationState.Unread;
this.setState({
appsRes: LOADING_REQUEST,
});
this.setState({
appsRes: await HttpService.client.listRegistrationApplications({
unread_only: unread_only,
page: this.state.page,
limit: fetchLimit,
}),
const appsRes = await HttpService.client.listRegistrationApplications({
unread_only: unread_only,
page: this.state.page,
limit: fetchLimit,
});
if (token === this.refetchToken) {
this.setState({ appsRes });
}
}
async handleApproveApplication(form: ApproveRegistrationApplication) {

View file

@ -56,6 +56,7 @@ import { UnreadCounterService } from "../../services";
import { getHttpBaseInternal } from "../../utils/env";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { isBrowser } from "@utils/browser";
enum UnreadOrAll {
Unread,
@ -160,8 +161,8 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.refetch();
}
}
@ -452,9 +453,22 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
}
all() {
const combined = this.buildCombined;
if (
combined.length === 0 &&
(this.state.commentReportsRes.state === "loading" ||
this.state.postReportsRes.state === "loading" ||
this.state.messageReportsRes.state === "loading")
) {
return (
<h5>
<Spinner large />
</h5>
);
}
return (
<div>
{this.buildCombined.map(i => (
{combined.map(i => (
<>
<hr />
{this.renderItemType(i)}
@ -575,6 +589,7 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
static async fetchInitialData({
headers,
site,
}: InitialFetchRequest): Promise<ReportsData> {
const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { headers }),
@ -601,7 +616,7 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
messageReportsRes: EMPTY_REQUEST,
};
if (amAdmin()) {
if (amAdmin(site.my_user)) {
const privateMessageReportsForm: ListPrivateMessageReports = {
unresolved_only,
page,
@ -616,7 +631,9 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
return data;
}
refetchToken?: symbol;
async refetch() {
const token = (this.refetchToken = Symbol());
const unresolved_only = this.state.unreadOrAll === UnreadOrAll.Unread;
const page = this.state.page;
const limit = fetchLimit;
@ -636,17 +653,30 @@ export class Reports extends Component<ReportsRouteProps, ReportsState> {
limit,
};
this.setState({
commentReportsRes: await HttpService.client.listCommentReports(form),
postReportsRes: await HttpService.client.listPostReports(form),
});
const commentReportPromise = HttpService.client
.listCommentReports(form)
.then(commentReportsRes => {
if (token === this.refetchToken) {
this.setState({ commentReportsRes });
}
});
const postReportPromise = HttpService.client
.listPostReports(form)
.then(postReportsRes => {
if (token === this.refetchToken) {
this.setState({ postReportsRes });
}
});
if (amAdmin()) {
this.setState({
messageReportsRes:
await HttpService.client.listPrivateMessageReports(form),
});
const messageReportsRes =
await HttpService.client.listPrivateMessageReports(form);
if (token === this.refetchToken) {
this.setState({ messageReportsRes });
}
}
await Promise.all([commentReportPromise, postReportPromise]);
}
async handleResolveCommentReport(form: ResolveCommentReport) {

View file

@ -22,17 +22,17 @@ import {
BlockCommunityResponse,
BlockInstanceResponse,
BlockPersonResponse,
CommunityBlockView,
CommentSortType,
Community,
GenerateTotpSecretResponse,
GetFederatedInstancesResponse,
GetSiteResponse,
Instance,
InstanceBlockView,
LemmyHttp,
ListingType,
LoginResponse,
PersonBlockView,
SortType,
Person,
PostSortType,
SuccessResponse,
UpdateTotpResponse,
} from "lemmy-js-client";
@ -65,13 +65,19 @@ import Tabs from "../common/tabs";
import { CommunityLink } from "../community/community-link";
import { PersonListing } from "./person-listing";
import { InitialFetchRequest } from "../../interfaces";
import TotpModal from "../common/totp-modal";
import TotpModal from "../common/modal/totp-modal";
import { LoadingEllipses } from "../common/loading-ellipses";
import { refreshTheme, setThemeOverride, snapToTop } from "../../utils/browser";
import {
isBrowser,
refreshTheme,
setThemeOverride,
snapToTop,
} from "../../utils/browser";
import { getHttpBaseInternal } from "../../utils/env";
import { IRoutePropsWithFetch } from "../../routes";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { simpleScrollMixin } from "../mixins/scroll-mixin";
import { CommentSortSelect } from "../common/comment-sort-select";
type SettingsData = RouteDataResponse<{
instancesRes: GetFederatedInstancesResponse;
@ -90,7 +96,8 @@ interface SettingsState {
blur_nsfw?: boolean;
auto_expand?: boolean;
theme?: string;
default_sort_type?: SortType;
default_post_sort_type?: PostSortType;
default_comment_sort_type?: CommentSortType;
default_listing_type?: ListingType;
interface_language?: string;
avatar?: string;
@ -108,7 +115,6 @@ interface SettingsState {
bot_account?: boolean;
show_bot_accounts?: boolean;
show_read_posts?: boolean;
show_new_post_notifs?: boolean;
discussion_languages?: number[];
open_links_in_new_tab?: boolean;
};
@ -121,9 +127,9 @@ interface SettingsState {
delete_content?: boolean;
password?: string;
};
personBlocks: PersonBlockView[];
communityBlocks: CommunityBlockView[];
instanceBlocks: InstanceBlockView[];
personBlocks: Person[];
communityBlocks: Community[];
instanceBlocks: Instance[];
currentTab: string;
themeList: string[];
deleteAccountShowConfirm: boolean;
@ -245,7 +251,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
constructor(props: any, context: any) {
super(props, context);
this.handleSortTypeChange = this.handleSortTypeChange.bind(this);
this.handlePostSortTypeChange = this.handlePostSortTypeChange.bind(this);
this.handleCommentSortTypeChange =
this.handleCommentSortTypeChange.bind(this);
this.handleListingTypeChange = this.handleListingTypeChange.bind(this);
this.handleBioChange = this.handleBioChange.bind(this);
this.handleDiscussionLanguageChange =
@ -273,9 +281,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
local_user: {
show_nsfw,
blur_nsfw,
auto_expand,
theme,
default_sort_type,
default_post_sort_type,
default_comment_sort_type,
default_listing_type,
interface_language,
show_avatars,
@ -310,9 +318,9 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
...this.state.saveUserSettingsForm,
show_nsfw,
blur_nsfw,
auto_expand,
theme: theme ?? "browser",
default_sort_type,
default_post_sort_type,
default_comment_sort_type,
default_listing_type,
interface_language,
discussion_languages: mui.discussion_languages,
@ -348,17 +356,19 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
}
}
async componentDidMount() {
this.setState({ themeList: await fetchThemeList() });
async componentWillMount() {
if (isBrowser()) {
this.setState({ themeList: await fetchThemeList() });
if (!this.state.isIsomorphic) {
this.setState({
instancesRes: LOADING_REQUEST,
});
if (!this.state.isIsomorphic) {
this.setState({
instancesRes: LOADING_REQUEST,
});
this.setState({
instancesRes: await HttpService.client.getFederatedInstances(),
});
this.setState({
instancesRes: await HttpService.client.getFederatedInstances(),
});
}
}
}
@ -509,6 +519,7 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
value={this.state.changePasswordForm.old_password}
onInput={linkEvent(this, this.handleOldPasswordChange)}
label={I18NextService.i18n.t("old_password")}
required={false}
/>
</div>
<div className="input-group mb-3">
@ -550,14 +561,14 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
<>
<h2 className="h5">{I18NextService.i18n.t("blocked_users")}</h2>
<ul className="list-unstyled mb-0">
{this.state.personBlocks.map(pb => (
<li key={pb.target.id}>
{this.state.personBlocks.map(p => (
<li key={p.id}>
<span>
<PersonListing person={pb.target} />
<PersonListing person={p} />
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, recipientId: pb.target.id },
{ ctx: this, recipientId: p.id },
this.handleUnblockPerson,
)}
data-tippy-content={I18NextService.i18n.t("unblock_user")}
@ -594,14 +605,14 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
<>
<h2 className="h5">{I18NextService.i18n.t("blocked_communities")}</h2>
<ul className="list-unstyled mb-0">
{this.state.communityBlocks.map(cb => (
<li key={cb.community.id}>
{this.state.communityBlocks.map(c => (
<li key={c.id}>
<span>
<CommunityLink community={cb.community} />
<CommunityLink community={c} />
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, communityId: cb.community.id },
{ ctx: this, communityId: c.id },
this.handleUnblockCommunity,
)}
data-tippy-content={I18NextService.i18n.t(
@ -639,14 +650,14 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
<>
<h2 className="h5">{I18NextService.i18n.t("blocked_instances")}</h2>
<ul className="list-unstyled mb-0">
{this.state.instanceBlocks.map(ib => (
<li key={ib.instance.id}>
{this.state.instanceBlocks.map(i => (
<li key={i.id}>
<span>
{ib.instance.domain}
{i.domain}
<button
className="btn btn-sm"
onClick={linkEvent(
{ ctx: this, instanceId: ib.instance.id },
{ ctx: this, instanceId: i.id },
this.handleUnblockInstance,
)}
data-tippy-content={I18NextService.i18n.t("unblock_instance")}
@ -900,14 +911,29 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
</form>
<form className="mb-3 row">
<label className="col-sm-3 col-form-label">
{I18NextService.i18n.t("sort_type")}
{I18NextService.i18n.t("post_sort_type")}
</label>
<div className="col-sm-9">
<SortSelect
sort={
this.state.saveUserSettingsForm.default_sort_type ?? "Active"
this.state.saveUserSettingsForm.default_post_sort_type ??
"Active"
}
onChange={this.handleSortTypeChange}
onChange={this.handlePostSortTypeChange}
/>
</div>
</form>
<form className="mb-3 row">
<label className="col-sm-3 col-form-label">
{I18NextService.i18n.t("comment_sort_type")}
</label>
<div className="col-sm-9">
<CommentSortSelect
sort={
this.state.saveUserSettingsForm.default_comment_sort_type ??
"Hot"
}
onChange={this.handleCommentSortTypeChange}
/>
</div>
</form>
@ -1086,23 +1112,6 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
</label>
</div>
</div>
<div className="input-group mb-3">
<div className="form-check">
<input
className="form-check-input"
id="user-show-new-post-notifs"
type="checkbox"
checked={this.state.saveUserSettingsForm.show_new_post_notifs}
onChange={linkEvent(this, this.handleShowNewPostNotifs)}
/>
<label
className="form-check-label"
htmlFor="user-show-new-post-notifs"
>
{I18NextService.i18n.t("show_new_post_notifs")}
</label>
</div>
</div>
<div className="input-group mb-3">
<div className="form-check">
<input
@ -1360,7 +1369,7 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
instance =>
instance.domain.toLowerCase().includes(text.toLowerCase()) &&
!this.state.instanceBlocks.some(
blockedInstance => blockedInstance.instance.id === instance.id,
blockedInstance => blockedInstance.id === instance.id,
),
) ?? [];
}
@ -1489,14 +1498,6 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
);
}
handleShowNewPostNotifs(i: Settings, event: any) {
i.setState(
s => (
(s.saveUserSettingsForm.show_new_post_notifs = event.target.checked), s
),
);
}
handleOpenInNewTab(i: Settings, event: any) {
i.setState(
s => (
@ -1600,8 +1601,16 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
);
}
handleSortTypeChange(val: SortType) {
this.setState(s => ((s.saveUserSettingsForm.default_sort_type = val), s));
handlePostSortTypeChange(val: PostSortType) {
this.setState(
s => ((s.saveUserSettingsForm.default_post_sort_type = val), s),
);
}
handleCommentSortTypeChange(val: CommentSortType) {
this.setState(
s => ((s.saveUserSettingsForm.default_comment_sort_type = val), s),
);
}
handleListingTypeChange(val: ListingType) {
@ -1701,12 +1710,12 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
const { new_password, new_password_verify, old_password } =
i.state.changePasswordForm;
if (new_password && old_password && new_password_verify) {
if (new_password && new_password_verify) {
i.setState({ changePasswordRes: LOADING_REQUEST });
const changePasswordRes = await HttpService.client.changePassword({
new_password,
new_password_verify,
old_password,
old_password: old_password || "",
});
if (changePasswordRes.state === "success") {
snapToTop();
@ -1763,14 +1772,13 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
local_user: {
show_nsfw,
blur_nsfw,
auto_expand,
theme,
default_sort_type,
default_post_sort_type,
default_comment_sort_type,
default_listing_type,
interface_language,
show_avatars,
show_bot_accounts,
show_scores,
show_read_posts,
send_notifications_to_email,
email,
@ -1803,18 +1811,17 @@ export class Settings extends Component<SettingsRouteProps, SettingsState> {
display_name,
bio,
matrix_user_id,
auto_expand,
blur_nsfw,
bot_account,
default_listing_type,
default_sort_type,
default_post_sort_type,
default_comment_sort_type,
discussion_languages: siteRes.data.my_user?.discussion_languages,
email,
interface_language,
open_links_in_new_tab,
send_notifications_to_email,
show_read_posts,
show_scores,
},
}));
}

View file

@ -13,6 +13,7 @@ import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { simpleScrollMixin } from "../mixins/scroll-mixin";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { isBrowser } from "@utils/browser";
interface State {
verifyRes: RequestState<SuccessResponse>;
@ -52,8 +53,10 @@ export class VerifyEmail extends Component<
}
}
async componentDidMount() {
await this.verify();
async componentWillMount() {
if (isBrowser()) {
await this.verify();
}
}
get documentTitle(): string {

View file

@ -5,8 +5,19 @@ import {
setIsoData,
voteDisplayMode,
} from "@utils/app";
import { getIdFromString, getQueryParams } from "@utils/helpers";
import { Choice, RouteDataResponse } from "@utils/types";
import {
bareRoutePush,
getIdFromString,
getQueryParams,
getQueryString,
} from "@utils/helpers";
import {
Choice,
CrossPostParams,
QueryParams,
RouteDataResponse,
StringBoolean,
} from "@utils/types";
import { Component } from "inferno";
import { RouteComponentProps } from "inferno-router/dist/Route";
import {
@ -27,14 +38,22 @@ import {
wrapClient,
} from "../../services/HttpService";
import { HtmlTags } from "../common/html-tags";
import { Spinner } from "../common/icon";
import { PostForm } from "./post-form";
import { getHttpBaseInternal } from "../../utils/env";
import { IRoutePropsWithFetch } from "../../routes";
import { simpleScrollMixin } from "../mixins/scroll-mixin";
import { toast } from "../../toast";
import { isBrowser } from "@utils/browser";
export interface CreatePostProps {
communityId?: number;
url?: string;
title?: string;
body?: string;
languageId?: number;
nsfw?: StringBoolean;
customThumbnailUrl?: string;
altText?: string;
}
type CreatePostData = RouteDataResponse<{
@ -46,6 +65,13 @@ export function getCreatePostQueryParams(source?: string): CreatePostProps {
return getQueryParams<CreatePostProps>(
{
communityId: getIdFromString,
url: (url?: string) => url,
body: (body?: string) => body,
languageId: getIdFromString,
nsfw: (nsfw?: StringBoolean) => nsfw,
customThumbnailUrl: (customThumbnailUrl?: string) => customThumbnailUrl,
title: (title?: string) => title,
altText: (altText?: string) => altText,
},
source,
);
@ -55,12 +81,17 @@ function fetchCommunitiesForOptions(client: WrappedLemmyHttp) {
return client.listCommunities({ limit: 30, sort: "TopMonth", type_: "All" });
}
function stringAsQueryParam(param?: string) {
return (param?.length ?? 0) > 0 ? param : undefined;
}
interface CreatePostState {
siteRes: GetSiteResponse;
loading: boolean;
selectedCommunityChoice?: Choice;
initialCommunitiesRes: RequestState<ListCommunitiesResponse>;
isIsomorphic: boolean;
resetCounter: number; // resets PostForm when changed
}
type CreatePostPathProps = Record<string, never>;
@ -80,9 +111,10 @@ export class CreatePost extends Component<
private isoData = setIsoData<CreatePostData>(this.context);
state: CreatePostState = {
siteRes: this.isoData.site_res,
loading: true,
loading: false,
initialCommunitiesRes: EMPTY_REQUEST,
isIsomorphic: false,
resetCounter: 0,
};
constructor(props: CreatePostRouteProps, context: any) {
@ -91,8 +123,16 @@ export class CreatePost extends Component<
this.handlePostCreate = this.handlePostCreate.bind(this);
this.handleSelectedCommunityChange =
this.handleSelectedCommunityChange.bind(this);
this.handleTitleBlur = this.handleTitleBlur.bind(this);
this.handleUrlBlur = this.handleUrlBlur.bind(this);
this.handleBodyBlur = this.handleBodyBlur.bind(this);
this.handleLanguageChange = this.handleLanguageChange.bind(this);
this.handleNsfwChange = this.handleNsfwChange.bind(this);
this.handleThumbnailUrlBlur = this.handleThumbnailUrlBlur.bind(this);
this.handleAltTextBlur = this.handleAltTextBlur.bind(this);
this.handleCopySuggestedTitle = this.handleCopySuggestedTitle.bind(this);
// Only fetch the data if coming from another route
// Only fetch the data if coming from another routeupdate
if (FirstLoadService.isFirstLoad) {
const { communityResponse: communityRes, initialCommunitiesRes } =
this.isoData.routeData;
@ -131,9 +171,9 @@ export class CreatePost extends Component<
}
}
async componentDidMount() {
async componentWillMount() {
// TODO test this
if (!this.state.isIsomorphic) {
if (!this.state.isIsomorphic && isBrowser()) {
const { communityId } = this.props;
const initialCommunitiesRes = await fetchCommunitiesForOptions(
@ -154,6 +194,30 @@ export class CreatePost extends Component<
loading: false,
});
}
const locationState = this.props.history.location.state as
| CrossPostParams
| undefined;
if (locationState) {
this.updateUrl({
title: locationState.name,
url: locationState.url,
body: locationState.body,
altText: locationState.altText,
nsfw: locationState.nsfw,
languageId: locationState.languageId,
});
this.setState(s => ({ resetCounter: s.resetCounter + 1 }));
}
}
}
componentWillReceiveProps(nextProps: CreatePostRouteProps) {
if (bareRoutePush(this.props, nextProps)) {
this.setState(s => ({ resetCounter: s.resetCounter + 1 }));
}
if (this.props.communityId !== nextProps.communityId) {
this.fetchCommunity(nextProps);
}
}
@ -164,11 +228,28 @@ export class CreatePost extends Component<
}
render() {
const { selectedCommunityChoice, siteRes } = this.state;
const { selectedCommunityChoice, siteRes, loading } = this.state;
const {
body,
communityId,
customThumbnailUrl,
languageId,
title,
nsfw,
url,
altText,
} = this.props;
const locationState = this.props.history.location.state as
| PostFormParams
| undefined;
const params: PostFormParams = {
name: title,
url,
body,
community_id: communityId,
custom_thumbnail: customThumbnailUrl,
language_id: languageId,
nsfw: nsfw === "true",
alt_text: altText,
};
return (
<div className="create-post container-lg">
@ -176,59 +257,71 @@ export class CreatePost extends Component<
title={this.documentTitle}
path={this.context.router.route.match.url}
/>
{this.state.loading ? (
<h5>
<Spinner large />
</h5>
) : (
<div className="row">
<div
id="createPostForm"
className="col-12 col-lg-6 offset-lg-3 mb-4"
>
<h1 className="h4 mb-4">
{I18NextService.i18n.t("create_post")}
</h1>
<PostForm
onCreate={this.handlePostCreate}
params={locationState}
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
enableNsfw={enableNsfw(siteRes)}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
selectedCommunityChoice={selectedCommunityChoice}
onSelectCommunity={this.handleSelectedCommunityChange}
initialCommunities={
this.state.initialCommunitiesRes.state === "success"
? this.state.initialCommunitiesRes.data.communities
: []
}
/>
</div>
<div className="row">
<div id="createPostForm" className="col-12 col-lg-6 offset-lg-3 mb-4">
<h1 className="h4 mb-4">{I18NextService.i18n.t("create_post")}</h1>
<PostForm
key={this.state.resetCounter}
onCreate={this.handlePostCreate}
params={params}
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
enableNsfw={enableNsfw(siteRes)}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
selectedCommunityChoice={selectedCommunityChoice}
onSelectCommunity={this.handleSelectedCommunityChange}
initialCommunities={
this.state.initialCommunitiesRes.state === "success"
? this.state.initialCommunitiesRes.data.communities
: []
}
loading={loading}
onBodyBlur={this.handleBodyBlur}
onLanguageChange={this.handleLanguageChange}
onTitleBlur={this.handleTitleBlur}
onUrlBlur={this.handleUrlBlur}
onThumbnailUrlBlur={this.handleThumbnailUrlBlur}
onNsfwChange={this.handleNsfwChange}
onAltTextBlur={this.handleAltTextBlur}
onCopySuggestedTitle={this.handleCopySuggestedTitle}
/>
</div>
)}
</div>
</div>
);
}
async updateUrl({ communityId }: Partial<CreatePostProps>) {
const locationState = this.props.history.location.state as
| PostFormParams
| undefined;
async updateUrl(props: Partial<CreatePostProps>) {
const {
body,
communityId,
customThumbnailUrl,
languageId,
nsfw,
url,
title,
altText,
} = {
...this.props,
...props,
};
const url = new URL(location.href);
const createPostQueryParams: QueryParams<CreatePostProps> = {
body: stringAsQueryParam(body),
communityId: communityId?.toString(),
customThumbnailUrl: stringAsQueryParam(customThumbnailUrl),
languageId: languageId?.toString(),
title: stringAsQueryParam(title),
nsfw,
url: stringAsQueryParam(url),
altText: stringAsQueryParam(altText),
};
const newId = communityId?.toString();
if (newId !== undefined) {
url.searchParams.set("communityId", newId);
} else {
url.searchParams.delete("communityId");
}
// This bypasses the router and doesn't update the query props.
window.history.replaceState(locationState, "", url);
this.props.history.replace({
pathname: "/create_post",
search: getQueryString(createPostQueryParams),
});
await this.fetchCommunity({ communityId });
}
@ -239,16 +332,51 @@ export class CreatePost extends Component<
});
}
async handlePostCreate(form: CreatePostI) {
handleTitleBlur(title: string) {
this.updateUrl({ title });
}
handleUrlBlur(url: string) {
this.updateUrl({ url });
}
handleBodyBlur(body: string) {
this.updateUrl({ body });
}
handleLanguageChange(languageId: number) {
this.updateUrl({ languageId });
}
handleNsfwChange(nsfw: StringBoolean) {
this.updateUrl({ nsfw });
}
handleThumbnailUrlBlur(customThumbnailUrl: string) {
this.updateUrl({ customThumbnailUrl });
}
handleAltTextBlur(altText: string) {
this.updateUrl({ altText });
}
handleCopySuggestedTitle(url: string, title: string) {
this.updateUrl({ url, title });
}
async handlePostCreate(form: CreatePostI, bypassNavWarning: () => void) {
this.setState({ loading: true });
const res = await HttpService.client.createPost(form);
if (res.state === "success") {
const postId = res.data.post_view.post.id;
bypassNavWarning();
this.props.history.replace(`/post/${postId}`);
} else {
} else if (res.state === "failed") {
this.setState({
loading: false,
});
toast(I18NextService.i18n.t(res.err.message), "danger");
}
}

View file

@ -8,9 +8,9 @@ import {
validURL,
} from "@utils/helpers";
import { isImage } from "@utils/media";
import { Choice } from "@utils/types";
import { Choice, StringBoolean } from "@utils/types";
import autosize from "autosize";
import { Component, InfernoNode, linkEvent } from "inferno";
import { Component, InfernoNode, createRef, linkEvent } from "inferno";
import { Prompt } from "inferno-router";
import {
CommunityView,
@ -27,7 +27,7 @@ import {
ghostArchiveUrl,
postMarkdownFieldCharacterLimit,
relTags,
trendingFetchLimit,
similarPostFetchLimit,
webArchiveUrl,
} from "../../config";
import { PostFormParams } from "../../interfaces";
@ -44,6 +44,15 @@ import { LanguageSelect } from "../common/language-select";
import { MarkdownTextArea } from "../common/markdown-textarea";
import { SearchableSelect } from "../common/searchable-select";
import { PostListings } from "./post-listings";
import { isBrowser } from "@utils/browser";
import isMagnetLink, {
extractMagnetLinkDownloadName,
} from "@utils/media/is-magnet-link";
import {
getUnixTimeLemmy,
getUnixTime,
unixTimeToLocalDateStr,
} from "@utils/helpers/get-unix-time";
const MAX_POST_TITLE_LENGTH = 200;
@ -54,14 +63,23 @@ interface PostFormProps {
siteLanguages: number[];
params?: PostFormParams;
onCancel?(): void;
onCreate?(form: CreatePost): void;
onEdit?(form: EditPost): void;
onCreate?(form: CreatePost, bypassNavWarning: () => void): void;
onEdit?(form: EditPost, bypassNavWarning: () => void): void;
enableNsfw?: boolean;
enableDownvotes?: boolean;
voteDisplayMode: LocalUserVoteDisplayMode;
selectedCommunityChoice?: Choice;
onSelectCommunity?: (choice: Choice) => void;
initialCommunities?: CommunityView[];
loading: boolean;
onTitleBlur?: (title: string) => void;
onUrlBlur?: (url: string) => void;
onBodyBlur?: (body: string) => void;
onLanguageChange?: (languageId?: number) => void;
onNsfwChange?: (nsfw: StringBoolean) => void;
onThumbnailUrlBlur?: (thumbnailUrl: string) => void;
onAltTextBlur?: (altText: string) => void;
onCopySuggestedTitle?: (url: string, title: string) => void;
}
interface PostFormState {
@ -75,8 +93,9 @@ interface PostFormState {
honeypot?: string;
custom_thumbnail?: string;
alt_text?: string;
// Javascript treats this field as a string, that can't have timezone info.
scheduled_publish_time?: string;
};
loading: boolean;
suggestedPostsRes: RequestState<SearchResponse>;
metadataRes: RequestState<GetSiteMetadataResponse>;
imageLoading: boolean;
@ -85,6 +104,7 @@ interface PostFormState {
communitySearchOptions: Choice[];
previewMode: boolean;
submitted: boolean;
bypassNavWarning: boolean;
}
function handlePostSubmit(i: PostForm, event: any) {
@ -93,48 +113,75 @@ function handlePostSubmit(i: PostForm, event: any) {
if ((i.state.form.url ?? "") === "") {
i.setState(s => ((s.form.url = undefined), s));
}
i.setState({ loading: true, submitted: true });
// This forces `props.loading` to become true, then false, to enable the
// submit button again.
i.setState({ submitted: true });
const pForm = i.state.form;
const pv = i.props.post_view;
const scheduled_publish_time = getUnixTimeLemmy(pForm.scheduled_publish_time);
if (pv) {
i.props.onEdit?.({
post_id: pv.post.id,
name: pForm.name,
url: pForm.url,
body: pForm.body,
nsfw: pForm.nsfw,
language_id: pForm.language_id,
custom_thumbnail: pForm.custom_thumbnail,
alt_text: pForm.alt_text,
});
i.props.onEdit?.(
{
post_id: pv.post.id,
name: pForm.name,
url: pForm.url,
body: pForm.body,
nsfw: pForm.nsfw,
language_id: pForm.language_id,
custom_thumbnail: pForm.custom_thumbnail,
alt_text: pForm.alt_text,
scheduled_publish_time,
},
() => {
i.setState({ bypassNavWarning: true });
},
);
} else if (pForm.name && pForm.community_id) {
i.props.onCreate?.({
name: pForm.name,
community_id: pForm.community_id,
url: pForm.url,
body: pForm.body,
nsfw: pForm.nsfw,
language_id: pForm.language_id,
honeypot: pForm.honeypot,
custom_thumbnail: pForm.custom_thumbnail,
alt_text: pForm.alt_text,
});
i.props.onCreate?.(
{
name: pForm.name,
community_id: pForm.community_id,
url: pForm.url,
body: pForm.body,
nsfw: pForm.nsfw,
language_id: pForm.language_id,
honeypot: pForm.honeypot,
custom_thumbnail: pForm.custom_thumbnail,
alt_text: pForm.alt_text,
scheduled_publish_time,
},
() => {
i.setState({ bypassNavWarning: true });
},
);
}
}
function copySuggestedTitle(d: { i: PostForm; suggestedTitle?: string }) {
const sTitle = d.suggestedTitle;
if (sTitle) {
d.i.setState(
s => ((s.form.name = sTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s),
function copySuggestedTitle({
i,
suggestedTitle,
}: {
i: PostForm;
suggestedTitle?: string;
}) {
if (suggestedTitle) {
i.setState(
s => (
(s.form.name = suggestedTitle?.substring(0, MAX_POST_TITLE_LENGTH)), s
),
);
d.i.setState({ suggestedPostsRes: EMPTY_REQUEST });
i.setState({ suggestedPostsRes: EMPTY_REQUEST });
setTimeout(() => {
const textarea: any = document.getElementById("post-title");
autosize.update(textarea);
if (i.postTitleRef.current) {
autosize.update(i.postTitleRef.current);
}
}, 10);
i.updateUrl(() =>
i.props.onCopySuggestedTitle?.(i.state.form.url!, suggestedTitle),
);
}
}
@ -153,8 +200,28 @@ function handlePostUrlChange(i: PostForm, event: any) {
i.fetchPageTitle();
}
function handlePostUrlBlur(i: PostForm, event: any) {
i.updateUrl(() => i.props.onUrlBlur?.(event.target.value));
}
function handlePostNsfwChange(i: PostForm, event: any) {
i.setState(s => ((s.form.nsfw = event.target.checked), s));
i.updateUrl(() =>
i.props.onNsfwChange?.(event.target.checked ? "true" : "false"),
);
}
function handlePostScheduleChange(i: PostForm, event: any) {
const scheduled_publish_time = event.target.value;
i.setState(prev => ({
...prev,
form: {
...prev.form,
scheduled_publish_time,
},
}));
}
function handleHoneyPotChange(i: PostForm, event: any) {
@ -165,10 +232,18 @@ function handleAltTextChange(i: PostForm, event: any) {
i.setState(s => ((s.form.alt_text = event.target.value), s));
}
function handleAltTextBlur(i: PostForm, event: any) {
i.updateUrl(() => i.props.onAltTextBlur?.(event.target.value));
}
function handleCustomThumbnailChange(i: PostForm, event: any) {
i.setState(s => ((s.form.custom_thumbnail = event.target.value), s));
}
function handleCustomThumbnailBlur(i: PostForm, event: any) {
i.updateUrl(() => i.props.onThumbnailUrlBlur?.(event.target.value));
}
function handleCancel(i: PostForm) {
i.props.onCancel?.();
}
@ -192,8 +267,6 @@ function handleImageUpload(i: PostForm, event: any) {
i.setState({ imageLoading: true });
HttpService.client.uploadImage({ image: file }).then(res => {
console.log("pictrs upload:");
console.log(res);
if (res.state === "success") {
if (res.data.msg === "ok") {
i.state.form.url = res.data.url;
@ -219,6 +292,10 @@ function handlePostNameChange(i: PostForm, event: any) {
i.fetchSimilarPosts();
}
function handlePostNameBlur(i: PostForm, event: any) {
i.updateUrl(() => i.props.onTitleBlur?.(event.target.value));
}
function handleImageDelete(i: PostForm) {
const { imageDeleteUrl } = i.state;
@ -240,27 +317,32 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
suggestedPostsRes: EMPTY_REQUEST,
metadataRes: EMPTY_REQUEST,
form: {},
loading: false,
imageLoading: false,
imageDeleteUrl: "",
communitySearchLoading: false,
previewMode: false,
communitySearchOptions: [],
submitted: false,
bypassNavWarning: false,
};
postTitleRef = createRef<HTMLTextAreaElement>();
constructor(props: PostFormProps, context: any) {
super(props, context);
this.fetchSimilarPosts = debounce(this.fetchSimilarPosts.bind(this));
this.fetchPageTitle = debounce(this.fetchPageTitle.bind(this));
this.handlePostBodyChange = this.handlePostBodyChange.bind(this);
this.handlePostBodyBlur = this.handlePostBodyBlur.bind(this);
this.handleLanguageChange = this.handleLanguageChange.bind(this);
this.handleCommunitySelect = this.handleCommunitySelect.bind(this);
this.updateUrl = this.updateUrl.bind(this);
const { post_view, selectedCommunityChoice, params } = this.props;
// Means its an edit
if (post_view) {
const unix = getUnixTime(post_view.post.scheduled_publish_time);
var scheduled_publish_time = unixTimeToLocalDateStr(unix);
this.state = {
...this.state,
form: {
@ -272,6 +354,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
language_id: post_view.post.language_id,
custom_thumbnail: post_view.post.thumbnail_url,
alt_text: post_view.post.alt_text,
scheduled_publish_time,
},
};
} else if (selectedCommunityChoice) {
@ -306,18 +389,26 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
}
}
componentDidMount() {
const textarea: any = document.getElementById("post-title");
componentWillMount() {
if (this.state.form.url && isBrowser()) {
this.fetchPageTitle();
}
}
if (textarea) {
autosize(textarea);
componentDidMount() {
if (this.postTitleRef.current) {
autosize(this.postTitleRef.current);
}
}
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & PostFormProps>,
): void {
if (this.props !== nextProps) {
if (
this.props.selectedCommunityChoice?.value !==
nextProps.selectedCommunityChoice?.value &&
nextProps.selectedCommunityChoice
) {
this.setState(
s => (
(s.form.community_id = getIdFromString(
@ -326,6 +417,33 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
s
),
);
this.setState({
communitySearchOptions: [nextProps.selectedCommunityChoice].concat(
(nextProps.initialCommunities?.map(communityToChoice) ?? []).filter(
option => option.value !== nextProps.selectedCommunityChoice?.value,
),
),
});
}
if (
!this.props.initialCommunities?.length &&
nextProps.initialCommunities?.length
) {
this.setState({
communitySearchOptions:
nextProps.initialCommunities?.map(communityToChoice) ?? [],
});
}
if (this.props.loading && !nextProps.loading) {
this.setState({ submitted: false, bypassNavWarning: false });
}
if (this.props.params !== nextProps.params && nextProps.params) {
const params = nextProps.params;
for (const k in params) {
if (this.props.params?.[k] !== params[k]) {
this.setState(s => ({ form: { ...s.form, [k]: params[k] } }));
}
}
}
}
@ -344,7 +462,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.state.form.name ||
this.state.form.url ||
this.state.form.body
) && !this.state.submitted
) && !this.state.bypassNavWarning
}
/>
<div className="mb-3 row">
@ -356,6 +474,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
value={this.state.form.name}
id="post-title"
onInput={linkEvent(this, handlePostNameChange)}
onBlur={linkEvent(this, handlePostNameBlur)}
className={`form-control ${
!validTitle(this.state.form.name) && "is-invalid"
}`}
@ -363,6 +482,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
rows={1}
minLength={3}
maxLength={MAX_POST_TITLE_LENGTH}
ref={this.postTitleRef}
/>
{!validTitle(this.state.form.name) && (
<div className="invalid-feedback">
@ -385,6 +505,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="form-control mb-3"
value={url}
onInput={linkEvent(this, handlePostUrlChange)}
onBlur={linkEvent(this, handlePostUrlBlur)}
onPaste={linkEvent(this, handleImageUploadPaste)}
/>
{this.renderSuggestedTitleCopy()}
@ -500,6 +621,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
className="form-control mb-3"
value={this.state.form.custom_thumbnail}
onInput={linkEvent(this, handleCustomThumbnailChange)}
onBlur={linkEvent(this, handleCustomThumbnailBlur)}
/>
</div>
</div>
@ -514,6 +636,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
initialContent={this.state.form.body}
placeholder={I18NextService.i18n.t("optional")}
onContentChange={this.handlePostBodyChange}
onContentBlur={this.handlePostBodyBlur}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
hideNavigationWarnings
@ -543,6 +666,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
id="post-alt-text"
value={this.state.form.alt_text}
onInput={linkEvent(this, handleAltTextChange)}
onBlur={linkEvent(this, handleAltTextBlur)}
/>
</div>
</div>
@ -584,6 +708,23 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
</label>
</div>
)}
<div className="mb-3 row">
<label className="col-sm-2 col-form-label" htmlFor="post-schedule">
{I18NextService.i18n.t("scheduled_publish_time")}
</label>
<div className="col-sm-10">
<input
type="datetime-local"
value={this.state.form.scheduled_publish_time}
min={unixTimeToLocalDateStr(Date.now())}
id="post-schedule"
className="form-control mb-3"
onInput={linkEvent(this, handlePostScheduleChange)}
/>
</div>
</div>
<input
tabIndex={-1}
autoComplete="false"
@ -597,11 +738,15 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
<div className="mb-3 row">
<div className="col-sm-10">
<button
disabled={!this.state.form.community_id || this.state.loading}
disabled={
!this.state.form.community_id ||
this.props.loading ||
this.state.submitted
}
type="submit"
className="btn btn-secondary me-2"
>
{this.state.loading ? (
{this.props.loading ? (
<Spinner />
) : this.props.post_view ? (
capitalizeFirstLetter(I18NextService.i18n.t("save"))
@ -705,10 +850,25 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
async fetchPageTitle() {
const url = this.state.form.url;
if (url && validURL(url)) {
this.setState({ metadataRes: LOADING_REQUEST });
this.setState({
metadataRes: await HttpService.client.getSiteMetadata({ url }),
});
// If its a magnet link, fill in the download name
if (isMagnetLink(url)) {
const title = extractMagnetLinkDownloadName(url);
if (title) {
this.setState({
metadataRes: {
state: "success",
data: {
metadata: { title },
},
},
});
}
} else {
this.setState({ metadataRes: LOADING_REQUEST });
this.setState({
metadataRes: await HttpService.client.getSiteMetadata({ url }),
});
}
}
}
@ -724,7 +884,7 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
listing_type: "All",
community_id: this.state.form.community_id,
page: 1,
limit: trendingFetchLimit,
limit: similarPostFetchLimit,
}),
});
}
@ -734,8 +894,13 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
this.setState(s => ((s.form.body = val), s));
}
handlePostBodyBlur(val: string) {
this.updateUrl(() => this.props.onBodyBlur?.(val));
}
handleLanguageChange(val: number[]) {
this.setState(s => ((s.form.language_id = val.at(0)), s));
this.updateUrl(() => this.props.onLanguageChange?.(val.at(0)));
}
handleCommunitySearch = debounce(async (text: string) => {
@ -762,8 +927,12 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
});
handleCommunitySelect(choice: Choice) {
if (this.props.onSelectCommunity) {
this.props.onSelectCommunity(choice);
}
this.updateUrl(() => this.props.onSelectCommunity?.(choice));
}
updateUrl(update: () => void) {
this.setState({ bypassNavWarning: true });
update();
this.setState({ bypassNavWarning: false });
}
}

View file

@ -1,12 +1,13 @@
import { myAuth, setIsoData } from "@utils/app";
import { canShare, share } from "@utils/browser";
import { getExternalHost, getHttpBase } from "@utils/env";
import { futureDaysToUnixTime, hostname } from "@utils/helpers";
import { formatPastDate, futureDaysToUnixTime, hostname } from "@utils/helpers";
import { isImage, isVideo } from "@utils/media";
import { canAdmin, canMod } from "@utils/roles";
import classNames from "classnames";
import { Component, linkEvent } from "inferno";
import { Link } from "inferno-router";
import { T } from "inferno-i18next-dess";
import {
AddAdmin,
AddModToCommunity,
@ -33,7 +34,7 @@ import {
SavePost,
TransferCommunity,
} from "lemmy-js-client";
import { relTags } from "../../config";
import { relTags, torrentHelpUrl } from "../../config";
import { IsoDataOptionalSite, VoteContentType } from "../../interfaces";
import { mdToHtml, mdToHtmlInline } from "../../markdown";
import { I18NextService, UserService } from "../../services";
@ -47,10 +48,14 @@ import { CommunityLink } from "../community/community-link";
import { PersonListing } from "../person/person-listing";
import { MetadataCard } from "./metadata-card";
import { PostForm } from "./post-form";
import { BanUpdateForm } from "../common/mod-action-form-modal";
import { BanUpdateForm } from "../common/modal/mod-action-form-modal";
import PostActionDropdown from "../common/content-actions/post-action-dropdown";
import { CrossPostParams } from "@utils/types";
import { RequestState } from "../../services/HttpService";
import { toast } from "../../toast";
import isMagnetLink, {
extractMagnetLinkDownloadName,
} from "@utils/media/is-magnet-link";
type PostListingState = {
showEdit: boolean;
@ -58,6 +63,7 @@ type PostListingState = {
viewSource: boolean;
showAdvanced: boolean;
showBody: boolean;
loading: boolean;
};
interface PostListingProps {
@ -107,6 +113,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
viewSource: false,
showAdvanced: false,
showBody: false,
loading: false,
};
constructor(props: any, context: any) {
@ -134,17 +141,30 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
this.handleHidePost = this.handleHidePost.bind(this);
}
componentDidMount(): void {
unlisten = () => {};
componentWillMount(): void {
if (
UserService.Instance.myUserInfo &&
!this.isoData.showAdultConsentModal
) {
const { auto_expand, blur_nsfw } =
UserService.Instance.myUserInfo.local_user_view.local_user;
const blur_nsfw =
UserService.Instance.myUserInfo.local_user_view.local_user.blur_nsfw;
this.setState({
imageExpanded: auto_expand && !(blur_nsfw && this.postView.post.nsfw),
imageExpanded: !(blur_nsfw && this.postView.post.nsfw),
});
}
// Leave edit mode on navigation
this.unlisten = this.context.router.history.listen(() => {
if (this.state.showEdit) {
this.setState({ showEdit: false });
}
});
}
componentWillUnmount(): void {
this.unlisten();
}
get postView(): PostView {
@ -160,9 +180,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
<>
{this.listing()}
{this.state.imageExpanded && !this.props.hideImage && this.img}
{this.showBody &&
post.url &&
isMagnetLink(post.url) &&
this.torrentHelp()}
{this.showBody && post.url && post.embed_title && (
<MetadataCard post={post} />
)}
{this.showBody && this.videoBlock}
{this.showBody && this.body()}
</>
) : (
@ -176,6 +201,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
voteDisplayMode={this.props.voteDisplayMode}
allLanguages={this.props.allLanguages}
siteLanguages={this.props.siteLanguages}
loading={this.state.loading}
/>
)}
</div>
@ -200,9 +226,54 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
);
}
torrentHelp() {
return (
<div className="alert alert-info small my-2" role="alert">
<Icon icon="info" classes="icon-inline me-2" />
<T parent="span" i18nKey="torrent_help">
#
<a className="alert-link" rel={relTags} href={torrentHelpUrl}>
#
</a>
</T>
</div>
);
}
get videoBlock() {
const post = this.postView.post;
const url = post.url;
// if direct video link or embedded video link
if (url && isVideo(url)) {
return (
<div className="ratio ratio-16x9 mt-3">
<video
onLoadStart={linkEvent(this, this.handleVideoLoadStart)}
onPlay={linkEvent(this, this.handleVideoLoadStart)}
onVolumeChange={linkEvent(this, this.handleVideoVolumeChange)}
controls
>
<source src={post.embed_video_url ?? url} type="video/mp4" />
</video>
</div>
);
} else if (post.embed_video_url) {
return (
<div className="ratio ratio-16x9 mt-3">
<iframe
title="video embed"
src={post.embed_video_url}
sandbox="allow-same-origin allow-scripts"
allowFullScreen={true}
></iframe>
</div>
);
}
}
get img() {
const { post } = this.postView;
const { url } = post;
if (this.isoData.showAdultConsentModal) {
return <></>;
@ -229,37 +300,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
);
}
// if direct video link
if (url && isVideo(url)) {
return (
<div className="embed-responsive ratio ratio-16x9 mt-3">
<video
onLoadStart={linkEvent(this, this.handleVideoLoadStart)}
onPlay={linkEvent(this, this.handleVideoLoadStart)}
onVolumeChange={linkEvent(this, this.handleVideoVolumeChange)}
controls
className="embed-responsive-item col-12"
>
<source src={url} type="video/mp4" />
</video>
</div>
);
}
// if embedded video link
if (url && post.embed_video_url) {
return (
<div className="ratio ratio-16x9">
<iframe
allowFullScreen
className="post-metadata-iframe"
src={post.embed_video_url}
title={post.embed_title}
></iframe>
</div>
);
}
return <></>;
}
@ -310,7 +350,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
/>
</button>
);
} else if (!this.props.hideImage && url && thumbnail && this.imageSrc) {
} else if (
!this.props.hideImage &&
url &&
thumbnail &&
this.imageSrc &&
!isVideo(url)
) {
return (
<a
className="thumbnail rounded overflow-hidden d-inline-block position-relative p-0 border-0"
@ -330,7 +376,12 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
if ((!this.props.hideImage && isVideo(url)) || post.embed_video_url) {
return (
<a
className="text-body"
className={classNames(
"thumbnail rounded",
thumbnail
? "overflow-hidden d-inline-block position-relative p-0 border-0"
: "text-body bg-light d-flex justify-content-center",
)}
href={url}
title={url}
rel={relTags}
@ -339,9 +390,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
aria-label={I18NextService.i18n.t("expand_here")}
target={this.linkTarget}
>
<div className="thumbnail rounded bg-light d-flex justify-content-center">
<Icon icon="play" classes="d-flex align-items-center" />
</div>
{thumbnail && this.imgThumb(thumbnail)}
<Icon
icon="video"
classes={
thumbnail
? "d-block text-white position-absolute end-0 top-0 mini-overlay text-opacity-75 text-opacity-100-hover"
: "d-flex align-items-center"
}
/>
</a>
);
} else {
@ -376,7 +433,6 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
createdLine() {
const pv = this.postView;
return (
<div className="small mb-1 mb-md-0">
<PersonListing person={pv.creator} />
@ -402,6 +458,13 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
</span>
)}{" "}
{pv.post.scheduled_publish_time && (
<span className="mx-1 badge text-bg-light">
{I18NextService.i18n.t("publish_in_time", {
time: formatPastDate(pv.post.scheduled_publish_time),
})}
</span>
)}{" "}
· <MomentTime published={pv.post.published} updated={pv.post.updated} />
</div>
);
@ -522,20 +585,31 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
const post = this.postView.post;
const url = post.url;
return (
<p className="small m-0">
{url && !(hostname(url) === getExternalHost()) && (
<a
className="fst-italic link-dark link-opacity-75 link-opacity-100-hover"
href={url}
title={url}
rel={relTags}
>
{hostname(url)}
</a>
)}
</p>
);
if (url) {
// If its a torrent link, extract the download name
const linkName = isMagnetLink(url)
? extractMagnetLinkDownloadName(url)
: !(hostname(url) === getExternalHost())
? hostname(url)
: null;
if (linkName) {
return (
<p className="small m-0">
{url && !(hostname(url) === getExternalHost()) && (
<a
className="fst-italic link-dark link-opacity-75 link-opacity-100-hover"
href={url}
title={url}
rel={relTags}
>
{linkName}
</a>
)}
</p>
);
}
}
}
duplicatesLine() {
@ -701,19 +775,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
mobileThumbnail() {
const post = this.postView.post;
return post.thumbnail_url || (post.url && isImage(post.url)) ? (
return (
<div className="row">
<div className={`${this.state.imageExpanded ? "col-12" : "col-9"}`}>
{this.postTitleLine()}
</div>
<div className="col-9">{this.postTitleLine()}</div>
<div className="col-3 mobile-thumbnail-container">
{/* Post thumbnail */}
{!this.state.imageExpanded && this.thumbnail()}
{this.thumbnail()}
</div>
</div>
) : (
this.postTitleLine()
);
}
@ -812,9 +881,17 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
// The actual editing is done in the receive for post
handleEditPost(form: EditPost) {
this.setState({ showEdit: false });
return this.props.onPostEdit(form);
async handleEditPost(form: EditPost) {
this.setState({ loading: true });
const res = await this.props.onPostEdit(form);
if (res.state === "success") {
toast(I18NextService.i18n.t("edited_post"));
this.setState({ loading: false, showEdit: false });
} else if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
this.setState({ loading: false });
}
}
handleShare(i: PostListing) {
@ -855,7 +932,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
}
get crossPostParams(): CrossPostParams {
const { name, url } = this.postView.post;
const { name, url, alt_text, nsfw, language_id } = this.postView.post;
const crossPostParams: CrossPostParams = { name };
if (url) {
@ -867,6 +944,18 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
crossPostParams.body = crossPostBody;
}
if (alt_text) {
crossPostParams.altText = alt_text;
}
if (nsfw) {
crossPostParams.nsfw = nsfw ? "true" : "false";
}
if (language_id !== undefined) {
crossPostParams.languageId = language_id;
}
return crossPostParams;
}
@ -940,7 +1029,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
handleModBanFromCommunity({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id },
@ -951,7 +1040,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
@ -959,7 +1048,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
community_id,
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});
@ -968,7 +1057,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
handleModBanFromSite({
daysUntilExpires,
reason,
shouldRemove,
shouldRemoveOrRestoreData,
}: BanUpdateForm) {
const {
creator: { id: person_id, banned },
@ -977,14 +1066,14 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
// If its an unban, restore all their data
if (ban === false) {
shouldRemove = false;
shouldRemoveOrRestoreData = true;
}
const expires = futureDaysToUnixTime(daysUntilExpires);
return this.props.onBanPerson({
person_id,
ban,
remove_data: shouldRemove,
remove_or_restore_data: shouldRemoveOrRestoreData,
reason,
expires,
});

View file

@ -18,15 +18,17 @@ import { isBrowser } from "@utils/browser";
import {
debounce,
getApubName,
getQueryParams,
getQueryString,
randomStr,
resourcesSettled,
bareRoutePush,
} from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { isImage } from "@utils/media";
import { RouteDataResponse } from "@utils/types";
import autosize from "autosize";
import { QueryParams, RouteDataResponse } from "@utils/types";
import classNames from "classnames";
import { Component, RefObject, createRef, linkEvent } from "inferno";
import { Component, createRef, linkEvent } from "inferno";
import {
AddAdmin,
AddModToCommunity,
@ -103,6 +105,7 @@ import { PostListing } from "./post-listing";
import { getHttpBaseInternal } from "../../utils/env";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { compareAsc, compareDesc } from "date-fns";
const commentsShownInterval = 15;
@ -112,48 +115,135 @@ type PostData = RouteDataResponse<{
}>;
interface PostState {
postId?: number;
commentId?: number;
postRes: RequestState<GetPostResponse>;
commentsRes: RequestState<GetCommentsResponse>;
commentSort: CommentSortType;
commentViewType: CommentViewType;
scrolled?: boolean;
siteRes: GetSiteResponse;
commentSectionRef?: RefObject<HTMLDivElement>;
showSidebarMobile: boolean;
maxCommentsShown: number;
finished: Map<CommentId, boolean | undefined>;
isIsomorphic: boolean;
lastCreatedCommentId?: CommentId;
}
type PostPathProps =
| { post_id: string; comment_id: never }
| { post_id: never; comment_id: string };
type PostRouteProps = RouteComponentProps<PostPathProps> &
Record<string, never>;
function getCommentSortTypeFromQuery(
source: string | undefined,
fallback: CommentSortType,
): CommentSortType {
if (!source) {
return fallback;
}
switch (source) {
case "Hot":
case "Top":
case "New":
case "Old":
case "Controversial":
return source;
default:
return fallback;
}
}
function getQueryStringFromCommentSortType(
sort: CommentSortType,
siteRes: GetSiteResponse,
): undefined | string {
const myUserInfo = siteRes.my_user ?? UserService.Instance.myUserInfo;
const local_user = myUserInfo?.local_user_view.local_user;
const local_site = siteRes.site_view.local_site;
const defaultSort =
local_user?.default_comment_sort_type ??
local_site.default_comment_sort_type;
if (sort === defaultSort) {
return undefined;
}
return sort;
}
const defaultCommentView: CommentViewType = CommentViewType.Tree;
function getCommentViewTypeFromQuery(source?: string): CommentViewType {
switch (source) {
case "Tree":
return CommentViewType.Tree;
case "Flat":
return CommentViewType.Flat;
default:
return defaultCommentView;
}
}
function getQueryStringFromCommentView(
view: CommentViewType,
): string | undefined {
if (view === defaultCommentView) {
return undefined;
}
switch (view) {
case CommentViewType.Tree:
return "Tree";
case CommentViewType.Flat:
return "Flat";
default:
return undefined;
}
}
interface PostProps {
sort: CommentSortType;
view: CommentViewType;
scrollToComments: boolean;
}
type Fallbacks = {
sort: CommentSortType;
};
export function getPostQueryParams(
source: string | undefined,
siteRes: GetSiteResponse,
): PostProps {
const myUserInfo = siteRes.my_user ?? UserService.Instance.myUserInfo;
const local_user = myUserInfo?.local_user_view.local_user;
const local_site = siteRes.site_view.local_site;
return getQueryParams<PostProps, Fallbacks>(
{
scrollToComments: (s?: string) => !!s,
sort: getCommentSortTypeFromQuery,
view: getCommentViewTypeFromQuery,
},
source,
{
sort:
local_user?.default_comment_sort_type ??
local_site.default_comment_sort_type,
},
);
}
type PostPathProps = { post_id?: string; comment_id?: string };
type PostRouteProps = RouteComponentProps<PostPathProps> & PostProps;
type PartialPostRouteProps = Partial<
PostProps & { match: { params: PostPathProps } }
>;
export type PostFetchConfig = IRoutePropsWithFetch<
PostData,
PostPathProps,
Record<string, never>
PostProps
>;
@scrollMixin
export class Post extends Component<PostRouteProps, PostState> {
private isoData = setIsoData<PostData>(this.context);
private commentScrollDebounced: () => void;
private shouldScrollToComments: boolean = false;
private commentSectionRef = createRef<HTMLDivElement>();
state: PostState = {
postRes: EMPTY_REQUEST,
commentsRes: EMPTY_REQUEST,
postId: getIdFromProps(this.props),
commentId: getCommentIdFromProps(this.props),
commentSort: "Hot",
commentViewType: CommentViewType.Tree,
scrolled: false,
siteRes: this.isoData.site_res,
showSidebarMobile: false,
maxCommentsShown: commentsShownInterval,
finished: new Map(),
isIsomorphic: false,
};
@ -170,6 +260,8 @@ export class Post extends Component<PostRouteProps, PostState> {
this.handleFollow = this.handleFollow.bind(this);
this.handleModRemoveCommunity = this.handleModRemoveCommunity.bind(this);
this.handleCreateComment = this.handleCreateComment.bind(this);
this.handleCreateToplevelComment =
this.handleCreateToplevelComment.bind(this);
this.handleEditComment = this.handleEditComment.bind(this);
this.handleSaveComment = this.handleSaveComment.bind(this);
this.handleBlockCommunity = this.handleBlockCommunity.bind(this);
@ -201,8 +293,6 @@ export class Post extends Component<PostRouteProps, PostState> {
this.handleScrollIntoCommentsClick =
this.handleScrollIntoCommentsClick.bind(this);
this.state = { ...this.state, commentSectionRef: createRef() };
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
const { commentsRes, postRes } = this.isoData.routeData;
@ -213,71 +303,104 @@ export class Post extends Component<PostRouteProps, PostState> {
commentsRes,
isIsomorphic: true,
};
if (isBrowser()) {
if (this.checkScrollIntoCommentsParam) {
this.scrollIntoCommentSection();
}
}
}
}
async fetchPost() {
this.setState({
postRes: LOADING_REQUEST,
commentsRes: LOADING_REQUEST,
fetchPostToken?: symbol;
async fetchPost(props: PostRouteProps) {
const token = (this.fetchPostToken = Symbol());
this.setState({ postRes: LOADING_REQUEST });
const postRes = await HttpService.client.getPost({
id: getIdFromProps(props),
comment_id: getCommentIdFromProps(props),
});
if (token === this.fetchPostToken) {
this.setState({ postRes });
}
}
const [postRes, commentsRes] = await Promise.all([
await HttpService.client.getPost({
id: this.state.postId,
comment_id: this.state.commentId,
}),
HttpService.client.getComments({
post_id: this.state.postId,
parent_id: this.state.commentId,
max_depth: commentTreeMaxDepth,
sort: this.state.commentSort,
type_: "All",
saved_only: false,
}),
]);
this.setState({
postRes,
commentsRes,
fetchCommentsToken?: symbol;
async fetchComments(props: PostRouteProps) {
const token = (this.fetchCommentsToken = Symbol());
const { sort } = props;
this.setState({ commentsRes: LOADING_REQUEST });
const commentsRes = await HttpService.client.getComments({
post_id: getIdFromProps(props),
parent_id: getCommentIdFromProps(props),
max_depth: commentTreeMaxDepth,
sort,
type_: "All",
saved_only: false,
});
if (token === this.fetchCommentsToken) {
this.setState({ commentsRes });
}
}
if (this.checkScrollIntoCommentsParam) {
this.scrollIntoCommentSection();
updateUrl(props: PartialPostRouteProps, replace = false) {
const {
view,
sort,
match: {
params: { comment_id, post_id },
},
} = {
...this.props,
...props,
};
const query: QueryParams<PostProps> = {
sort: getQueryStringFromCommentSortType(sort, this.state.siteRes),
view: getQueryStringFromCommentView(view),
};
// Not inheriting old scrollToComments
if (props.scrollToComments) {
query.scrollToComments = true.toString();
}
let pathname: string | undefined;
if (comment_id && post_id) {
pathname = `/post/${post_id}/${comment_id}`;
} else if (comment_id) {
pathname = `/comment/${comment_id}`;
} else {
pathname = `/post/${post_id}`;
}
const location = { pathname, search: getQueryString(query) };
if (replace || this.props.location.pathname === pathname) {
this.props.history.replace(location);
} else {
this.props.history.push(location);
}
}
static async fetchInitialData({
headers,
match,
}: InitialFetchRequest<PostPathProps>): Promise<PostData> {
query: { sort },
}: InitialFetchRequest<PostPathProps, PostProps>): Promise<PostData> {
const client = wrapClient(
new LemmyHttp(getHttpBaseInternal(), { headers }),
);
const postId = getIdFromProps({ match });
const commentId = getCommentIdFromProps({ match });
const postForm: GetPost = {};
const postForm: GetPost = {
id: postId,
comment_id: commentId,
};
const commentsForm: GetComments = {
post_id: postId,
parent_id: commentId,
max_depth: commentTreeMaxDepth,
sort: "Hot",
sort,
type_: "All",
saved_only: false,
};
postForm.id = postId;
postForm.comment_id = commentId;
commentsForm.post_id = postId;
commentsForm.parent_id = commentId;
const [postRes, commentsRes] = await Promise.all([
client.getPost(postForm),
client.getComments(commentsForm),
@ -293,15 +416,79 @@ export class Post extends Component<PostRouteProps, PostState> {
document.removeEventListener("scroll", this.commentScrollDebounced);
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
await this.fetchPost();
async componentWillMount() {
if (isBrowser()) {
this.shouldScrollToComments = this.props.scrollToComments;
if (!this.state.isIsomorphic) {
await Promise.all([
this.fetchPost(this.props),
this.fetchComments(this.props),
]);
}
}
}
autosize(document.querySelectorAll("textarea"));
componentDidMount() {
this.commentScrollDebounced = debounce(this.trackCommentsBoxScrolling, 100);
document.addEventListener("scroll", this.commentScrollDebounced);
if (this.state.isIsomorphic) {
this.maybeScrollToComments();
}
}
componentWillReceiveProps(nextProps: PostRouteProps): void {
const { post_id: nextPost, comment_id: nextComment } =
nextProps.match.params;
const { post_id: prevPost, comment_id: prevComment } =
this.props.match.params;
const newOrder =
this.props.sort !== nextProps.sort || this.props.view !== nextProps.view;
// For comment links restore sort type from current props.
if (
nextPost === prevPost &&
nextComment &&
newOrder &&
!nextProps.location.search &&
nextProps.history.action === "PUSH"
) {
this.updateUrl({ match: nextProps.match }, true);
return;
}
const needPost =
prevPost !== nextPost ||
(bareRoutePush(this.props, nextProps) && !nextComment);
const needComments =
needPost ||
prevComment !== nextComment ||
nextProps.sort !== this.props.sort;
if (needPost) {
this.fetchPost(nextProps);
}
if (needComments) {
this.fetchComments(nextProps);
}
if (
nextProps.scrollToComments &&
this.props.scrollToComments !== nextProps.scrollToComments
) {
this.shouldScrollToComments = true;
}
}
componentDidUpdate(): void {
if (
this.commentSectionRef.current &&
this.state.postRes.state === "success" &&
this.state.commentsRes.state === "success"
) {
this.maybeScrollToComments();
}
}
handleScrollIntoCommentsClick(e: MouseEvent) {
@ -309,16 +496,18 @@ export class Post extends Component<PostRouteProps, PostState> {
e.preventDefault();
}
get checkScrollIntoCommentsParam() {
return (
Boolean(
new URLSearchParams(this.props.location.search).get("scrollToComments"),
) && this.props.history.action !== "POP"
);
maybeScrollToComments() {
if (this.shouldScrollToComments) {
this.shouldScrollToComments = false;
if (this.props.history.action !== "POP" || this.state.isIsomorphic) {
this.scrollIntoCommentSection();
}
}
}
scrollIntoCommentSection() {
this.state.commentSectionRef?.current?.scrollIntoView();
// This doesn't work when in a background tab in firefox.
this.commentSectionRef.current?.scrollIntoView();
}
isBottom(el: Element): boolean {
@ -413,20 +602,25 @@ export class Post extends Component<PostRouteProps, PostState> {
onHidePost={this.handleHidePost}
onScrollIntoCommentsClick={this.handleScrollIntoCommentsClick}
/>
<div ref={this.state.commentSectionRef} className="mb-2" />
<div ref={this.commentSectionRef} className="mb-2" />
{/* Only show the top level comment form if its not a context view */}
{!(
this.state.commentId || res.post_view.banned_from_community
getCommentIdFromProps(this.props) ||
res.post_view.banned_from_community
) && (
<CommentForm
key={
this.context.router.history.location.key +
this.state.lastCreatedCommentId
// reset on new location, otherwise <Prompt /> stops working
}
node={res.post_view.post.id}
disabled={res.post_view.post.locked}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
containerClass="post-comment-container"
onUpsertComment={this.handleCreateComment}
finished={this.state.finished.get(0)}
onUpsertComment={this.handleCreateToplevelComment}
/>
)}
<div className="d-block d-md-none">
@ -447,10 +641,8 @@ export class Post extends Component<PostRouteProps, PostState> {
{this.state.showSidebarMobile && this.sidebar()}
</div>
{this.sortRadios()}
{this.state.commentViewType === CommentViewType.Tree &&
this.commentsTree()}
{this.state.commentViewType === CommentViewType.Flat &&
this.commentsFlat()}
{this.props.view === CommentViewType.Tree && this.commentsTree()}
{this.props.view === CommentViewType.Flat && this.commentsFlat()}
</main>
<aside className="d-none d-md-block col-md-4 col-lg-3">
{this.sidebar()}
@ -482,13 +674,13 @@ export class Post extends Component<PostRouteProps, PostState> {
type="radio"
className="btn-check"
value={"Hot"}
checked={this.state.commentSort === "Hot"}
checked={this.props.sort === "Hot"}
onChange={linkEvent(this, this.handleCommentSortChange)}
/>
<label
htmlFor={`${radioId}-hot`}
className={classNames("btn btn-outline-secondary pointer", {
active: this.state.commentSort === "Hot",
active: this.props.sort === "Hot",
})}
>
{I18NextService.i18n.t("hot")}
@ -498,13 +690,13 @@ export class Post extends Component<PostRouteProps, PostState> {
type="radio"
className="btn-check"
value={"Top"}
checked={this.state.commentSort === "Top"}
checked={this.props.sort === "Top"}
onChange={linkEvent(this, this.handleCommentSortChange)}
/>
<label
htmlFor={`${radioId}-top`}
className={classNames("btn btn-outline-secondary pointer", {
active: this.state.commentSort === "Top",
active: this.props.sort === "Top",
})}
>
{I18NextService.i18n.t("top")}
@ -514,13 +706,13 @@ export class Post extends Component<PostRouteProps, PostState> {
type="radio"
className="btn-check"
value={"Controversial"}
checked={this.state.commentSort === "Controversial"}
checked={this.props.sort === "Controversial"}
onChange={linkEvent(this, this.handleCommentSortChange)}
/>
<label
htmlFor={`${radioId}-controversial`}
className={classNames("btn btn-outline-secondary pointer", {
active: this.state.commentSort === "Controversial",
active: this.props.sort === "Controversial",
})}
>
{I18NextService.i18n.t("controversial")}
@ -530,13 +722,13 @@ export class Post extends Component<PostRouteProps, PostState> {
type="radio"
className="btn-check"
value={"New"}
checked={this.state.commentSort === "New"}
checked={this.props.sort === "New"}
onChange={linkEvent(this, this.handleCommentSortChange)}
/>
<label
htmlFor={`${radioId}-new`}
className={classNames("btn btn-outline-secondary pointer", {
active: this.state.commentSort === "New",
active: this.props.sort === "New",
})}
>
{I18NextService.i18n.t("new")}
@ -546,13 +738,13 @@ export class Post extends Component<PostRouteProps, PostState> {
type="radio"
className="btn-check"
value={"Old"}
checked={this.state.commentSort === "Old"}
checked={this.props.sort === "Old"}
onChange={linkEvent(this, this.handleCommentSortChange)}
/>
<label
htmlFor={`${radioId}-old`}
className={classNames("btn btn-outline-secondary pointer", {
active: this.state.commentSort === "Old",
active: this.props.sort === "Old",
})}
>
{I18NextService.i18n.t("old")}
@ -564,13 +756,13 @@ export class Post extends Component<PostRouteProps, PostState> {
type="radio"
className="btn-check"
value={CommentViewType.Flat}
checked={this.state.commentViewType === CommentViewType.Flat}
checked={this.props.view === CommentViewType.Flat}
onChange={linkEvent(this, this.handleCommentViewTypeChange)}
/>
<label
htmlFor={`${radioId}-chat`}
className={classNames("btn btn-outline-secondary pointer", {
active: this.state.commentViewType === CommentViewType.Flat,
active: this.props.view === CommentViewType.Flat,
})}
>
{I18NextService.i18n.t("chat")}
@ -581,6 +773,14 @@ export class Post extends Component<PostRouteProps, PostState> {
}
commentsFlat() {
if (this.state.commentsRes.state === "loading") {
return (
<div className="text-center">
<Spinner large />
</div>
);
}
// These are already sorted by new
const commentsRes = this.state.commentsRes;
const postRes = this.state.postRes;
@ -590,8 +790,8 @@ export class Post extends Component<PostRouteProps, PostState> {
return (
<div>
<CommentNodes
nodes={commentsToFlatNodes(commentsRes.data.comments)}
viewType={this.state.commentViewType}
nodes={this.sortedFlatNodes()}
viewType={this.props.view}
maxCommentsShown={this.state.maxCommentsShown}
isTopLevel
locked={postRes.data.post_view.post.locked}
@ -600,7 +800,6 @@ export class Post extends Component<PostRouteProps, PostState> {
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
showContext
finished={this.state.finished}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
onSaveComment={this.handleSaveComment}
@ -652,7 +851,29 @@ export class Post extends Component<PostRouteProps, PostState> {
}
}
sortedFlatNodes(): CommentNodeI[] {
if (this.state.commentsRes.state !== "success") {
return [];
}
const nodeToDate = (node: CommentNodeI) =>
node.comment_view.comment.published;
const nodes = commentsToFlatNodes(this.state.commentsRes.data.comments);
if (this.props.sort === "New") {
return nodes.sort((a, b) => compareDesc(nodeToDate(a), nodeToDate(b)));
} else {
return nodes.sort((a, b) => compareAsc(nodeToDate(a), nodeToDate(b)));
}
}
commentsTree() {
if (this.state.commentsRes.state === "loading") {
return (
<div className="text-center">
<Spinner large />
</div>
);
}
const res = this.state.postRes;
const firstComment = this.commentTree().at(0)?.comment_view.comment;
const depth = getDepthFromComment(firstComment);
@ -662,11 +883,11 @@ export class Post extends Component<PostRouteProps, PostState> {
return (
res.state === "success" && (
<div>
{!!this.state.commentId && (
{!!getCommentIdFromProps(this.props) && (
<>
<button
className="ps-0 d-block btn btn-link text-muted"
onClick={linkEvent(this, this.handleViewPost)}
onClick={linkEvent(this, this.handleViewAllComments)}
>
{I18NextService.i18n.t("view_all_comments")}
</button>
@ -682,14 +903,13 @@ export class Post extends Component<PostRouteProps, PostState> {
)}
<CommentNodes
nodes={this.commentTree()}
viewType={this.state.commentViewType}
viewType={this.props.view}
maxCommentsShown={this.state.maxCommentsShown}
locked={res.data.post_view.post.locked}
moderators={res.data.moderators}
admins={siteRes.admins}
enableDownvotes={enableDownvotes(siteRes)}
voteDisplayMode={voteDisplayMode(siteRes)}
finished={this.state.finished}
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
onSaveComment={this.handleSaveComment}
@ -719,50 +939,69 @@ export class Post extends Component<PostRouteProps, PostState> {
commentTree(): CommentNodeI[] {
if (this.state.commentsRes.state === "success") {
return buildCommentsTree(
this.state.commentsRes.data.comments,
!!this.state.commentId,
);
} else {
return [];
const comments = this.state.commentsRes.data.comments;
if (comments.length) {
return buildCommentsTree(comments, !!getCommentIdFromProps(this.props));
}
}
return [];
}
async handleCommentSortChange(i: Post, event: any) {
i.setState({
commentSort: event.target.value as CommentSortType,
commentViewType: CommentViewType.Tree,
commentsRes: LOADING_REQUEST,
postRes: LOADING_REQUEST,
});
await i.fetchPost();
const sort = event.target.value as CommentSortType;
const flattenable = sort === "New" || sort === "Old";
if (flattenable || i.props.view !== CommentViewType.Flat) {
i.updateUrl({ sort });
} else {
i.updateUrl({ sort, view: CommentViewType.Tree });
}
}
handleCommentViewTypeChange(i: Post, event: any) {
i.setState({
commentViewType: Number(event.target.value),
commentSort: "New",
});
const flattenable = i.props.sort === "New" || i.props.sort === "Old";
const view: CommentViewType = Number(event.target.value);
if (flattenable || view !== CommentViewType.Flat) {
i.updateUrl({ view });
} else {
i.updateUrl({ view, sort: "New" });
}
}
handleShowSidebarMobile(i: Post) {
i.setState({ showSidebarMobile: !i.state.showSidebarMobile });
}
handleViewPost(i: Post) {
if (i.state.postRes.state === "success") {
const id = i.state.postRes.data.post_view.post.id;
i.context.router.history.push(`/post/${id}`);
handleViewAllComments(i: Post) {
const id =
getIdFromProps(i.props) ||
(i.state.postRes.state === "success" &&
i.state.postRes.data.post_view.post.id);
if (id) {
i.updateUrl({
match: { params: { post_id: id.toString() } },
});
}
}
handleViewContext(i: Post) {
if (i.state.commentsRes.state === "success") {
const parentId = getCommentParentId(
i.state.commentsRes.data.comments.at(0)?.comment,
const commentId = getCommentIdFromProps(i.props);
const commentView = i.state.commentsRes.data.comments.find(
c => c.comment.id === commentId,
);
if (parentId) {
i.context.router.history.push(`/comment/${parentId}`);
const parentId = getCommentParentId(commentView?.comment);
const postId = commentView?.post.id;
if (parentId && postId) {
i.updateUrl({
match: {
params: {
post_id: postId.toString(),
comment_id: parentId.toString(),
},
},
});
}
}
}
@ -848,6 +1087,14 @@ export class Post extends Component<PostRouteProps, PostState> {
return res;
}
async handleCreateToplevelComment(form: CreateComment) {
const res = await this.handleCreateComment(form);
if (res.state === "success") {
this.setState({ lastCreatedCommentId: res.data.comment_view.comment.id });
}
return res;
}
async handleCreateComment(form: CreateComment) {
const createCommentRes = await HttpService.client.createComment(form);
this.createAndUpdateComments(createCommentRes);
@ -1165,12 +1412,12 @@ export class Post extends Component<PostRouteProps, PostState> {
);
comments.splice(foundCommentParentIndex + 1, 0, newComment);
// Set finished for the parent
s.finished.set(newCommentParentId ?? 0, true);
}
return s;
});
if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
}
findAndUpdateCommentEdit(res: RequestState<CommentResponse>) {
@ -1180,13 +1427,14 @@ export class Post extends Component<PostRouteProps, PostState> {
res.data.comment_view,
s.commentsRes.data.comments,
);
s.finished.set(res.data.comment_view.comment.id, true);
}
return s;
});
if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
}
// No need to set finished on a comment vote, save, etc
findAndUpdateComment(res: RequestState<CommentResponse>) {
this.setState(s => {
if (s.commentsRes.state === "success" && res.state === "success") {
@ -1197,6 +1445,9 @@ export class Post extends Component<PostRouteProps, PostState> {
}
return s;
});
if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
}
findAndUpdateCommentReply(res: RequestState<CommentReplyResponse>) {
@ -1209,6 +1460,9 @@ export class Post extends Component<PostRouteProps, PostState> {
}
return s;
});
if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
}
updateModerators(res: RequestState<AddModToCommunityResponse>) {

View file

@ -26,6 +26,7 @@ import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../../routes";
import { resourcesSettled } from "@utils/helpers";
import { scrollMixin } from "../mixins/scroll-mixin";
import { isBrowser } from "@utils/browser";
type CreatePrivateMessageData = RouteDataResponse<{
recipientDetailsResponse: GetPersonDetailsResponse;
@ -79,8 +80,8 @@ export class CreatePrivateMessage extends Component<
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
await this.fetchPersonDetails();
}
}
@ -167,14 +168,22 @@ export class CreatePrivateMessage extends Component<
);
}
async handlePrivateMessageCreate(form: CreatePrivateMessageI) {
async handlePrivateMessageCreate(
form: CreatePrivateMessageI,
bypassNavWarning: () => void,
): Promise<boolean> {
const res = await HttpService.client.createPrivateMessage(form);
if (res.state === "success") {
toast(I18NextService.i18n.t("message_sent"));
bypassNavWarning();
// Navigate to the front
this.context.router.history.push("/");
} else if (res.state === "failed") {
toast(I18NextService.i18n.t(res.err.message), "danger");
}
return res.state !== "failed";
}
}

View file

@ -1,5 +1,5 @@
import { capitalizeFirstLetter } from "@utils/helpers";
import { Component, InfernoNode } from "inferno";
import { Component } from "inferno";
import { T } from "inferno-i18next-dess";
import { Prompt } from "inferno-router";
import {
@ -19,8 +19,14 @@ interface PrivateMessageFormProps {
privateMessageView?: PrivateMessageView; // If a pm is given, that means this is an edit
replyType?: boolean;
onCancel?(): any;
onCreate?(form: CreatePrivateMessage): void;
onEdit?(form: EditPrivateMessage): void;
onCreate?(
form: CreatePrivateMessage,
bypassNavWarning: () => void,
): Promise<boolean>;
onEdit?(
form: EditPrivateMessage,
bypassNavWarning: () => void,
): Promise<boolean>;
}
interface PrivateMessageFormState {
@ -28,6 +34,7 @@ interface PrivateMessageFormState {
loading: boolean;
previewMode: boolean;
submitted: boolean;
bypassNavWarning?: boolean;
}
export class PrivateMessageForm extends Component<
@ -51,21 +58,15 @@ export class PrivateMessageForm extends Component<
this.handlePrivateMessageSubmit.bind(this);
}
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageFormProps>,
): void {
if (this.props !== nextProps) {
this.setState({ loading: false, content: undefined, previewMode: false });
}
}
render() {
return (
<form className="private-message-form">
<Prompt
message={I18NextService.i18n.t("block_leaving")}
when={
!this.state.loading && !!this.state.content && !this.state.submitted
!this.state.bypassNavWarning &&
((!!this.state.content && !this.state.submitted) ||
this.state.loading)
}
/>
{!this.props.privateMessageView && (
@ -140,21 +141,34 @@ export class PrivateMessageForm extends Component<
);
}
handlePrivateMessageSubmit() {
async handlePrivateMessageSubmit(): Promise<boolean> {
this.setState({ loading: true, submitted: true });
const pm = this.props.privateMessageView;
const content = this.state.content ?? "";
let success: boolean | undefined;
if (pm) {
this.props.onEdit?.({
private_message_id: pm.private_message.id,
content,
});
success = await this.props.onEdit?.(
{
private_message_id: pm.private_message.id,
content,
},
() => {
this.setState({ bypassNavWarning: true });
},
);
} else {
this.props.onCreate?.({
content,
recipient_id: this.props.recipient.id,
});
success = await this.props.onCreate?.(
{
content,
recipient_id: this.props.recipient.id,
},
() => {
this.setState({ bypassNavWarning: true });
},
);
}
this.setState({ loading: false, submitted: success ?? true });
return success ?? true;
}
handleContentChange(val: string) {

View file

@ -1,4 +1,4 @@
import { Component, InfernoNode, linkEvent } from "inferno";
import { Component, linkEvent } from "inferno";
import {
CreatePrivateMessage,
CreatePrivateMessageReport,
@ -14,7 +14,7 @@ import { Icon, Spinner } from "../common/icon";
import { MomentTime } from "../common/moment-time";
import { PersonListing } from "../person/person-listing";
import { PrivateMessageForm } from "./private-message-form";
import ModActionFormModal from "../common/mod-action-form-modal";
import ModActionFormModal from "../common/modal/mod-action-form-modal";
import { tippyMixin } from "../mixins/tippy-mixin";
interface PrivateMessageState {
@ -32,8 +32,8 @@ interface PrivateMessageProps {
onDelete(form: DeletePrivateMessage): void;
onMarkRead(form: MarkPrivateMessageAsRead): void;
onReport(form: CreatePrivateMessageReport): void;
onCreate(form: CreatePrivateMessage): void;
onEdit(form: EditPrivateMessage): void;
onCreate(form: CreatePrivateMessage): Promise<boolean>;
onEdit(form: EditPrivateMessage): Promise<boolean>;
}
@tippyMixin
@ -56,6 +56,8 @@ export class PrivateMessage extends Component<
this.handleReplyCancel = this.handleReplyCancel.bind(this);
this.handleReportSubmit = this.handleReportSubmit.bind(this);
this.hideReportDialog = this.hideReportDialog.bind(this);
this.handleCreate = this.handleCreate.bind(this);
this.handleEdit = this.handleEdit.bind(this);
}
get mine(): boolean {
@ -65,22 +67,6 @@ export class PrivateMessage extends Component<
);
}
componentWillReceiveProps(
nextProps: Readonly<{ children?: InfernoNode } & PrivateMessageProps>,
): void {
if (this.props !== nextProps) {
this.setState({
showReply: false,
showEdit: false,
collapsed: false,
viewSource: false,
showReportDialog: false,
deleteLoading: false,
readLoading: false,
});
}
}
render() {
const message_view = this.props.private_message_view;
const otherPerson: Person = this.mine
@ -126,7 +112,7 @@ export class PrivateMessage extends Component<
<PrivateMessageForm
recipient={otherPerson}
privateMessageView={message_view}
onEdit={this.props.onEdit}
onEdit={this.handleEdit}
onCancel={this.handleReplyCancel}
/>
)}
@ -265,7 +251,7 @@ export class PrivateMessage extends Component<
<PrivateMessageForm
replyType={true}
recipient={otherPerson}
onCreate={this.props.onCreate}
onCreate={this.handleCreate}
onCancel={this.handleReplyCancel}
/>
</div>
@ -304,7 +290,6 @@ export class PrivateMessage extends Component<
handleEditClick(i: PrivateMessage) {
i.setState({ showEdit: true });
i.setState(i.state);
}
handleDeleteClick(i: PrivateMessage) {
@ -319,6 +304,22 @@ export class PrivateMessage extends Component<
this.setState({ showReply: false, showEdit: false });
}
async handleCreate(form: CreatePrivateMessage): Promise<boolean> {
const success = await this.props.onCreate(form);
if (success) {
this.setState({ showReply: false });
}
return success;
}
async handleEdit(form: EditPrivateMessage): Promise<boolean> {
const success = await this.props.onEdit(form);
if (success) {
this.setState({ showEdit: false });
}
return success;
}
handleMarkRead(i: PrivateMessage) {
i.setState({ readLoading: true });
i.props.onMarkRead({

View file

@ -25,6 +25,7 @@ import { CommunityLink } from "./community/community-link";
import { getHttpBaseInternal } from "../utils/env";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../routes";
import { isBrowser } from "@utils/browser";
interface RemoteFetchProps {
uri?: string;
@ -128,8 +129,8 @@ export class RemoteFetch extends Component<
}
}
async componentDidMount() {
if (!this.state.isIsomorphic) {
async componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
const { uri } = this.props;
if (uri) {

View file

@ -5,7 +5,6 @@ import {
enableNsfw,
fetchCommunities,
fetchUsers,
getUpdatedSearchId,
myAuth,
personToChoice,
setIsoData,
@ -19,6 +18,7 @@ import {
dedupByProperty,
getIdFromString,
getPageFromString,
getBoolFromString,
getQueryParams,
getQueryString,
numToSI,
@ -46,7 +46,7 @@ import {
Search as SearchForm,
SearchResponse,
SearchType,
SortType,
PostSortType,
} from "lemmy-js-client";
import { fetchLimit } from "../config";
import { CommentViewType, InitialFetchRequest } from "../interfaces";
@ -71,12 +71,14 @@ import { PostListing } from "./post/post-listing";
import { getHttpBaseInternal } from "../utils/env";
import { RouteComponentProps } from "inferno-router/dist/Route";
import { IRoutePropsWithFetch } from "../routes";
import { isBrowser } from "@utils/browser";
interface SearchProps {
q?: string;
type: SearchType;
sort: SortType;
sort: PostSortType;
listingType: ListingType;
titleOnly?: boolean;
communityId?: number;
creatorId?: number;
page: number;
@ -96,7 +98,6 @@ interface SearchState {
searchRes: RequestState<SearchResponse>;
resolveObjectRes: RequestState<ResolveObjectResponse>;
siteRes: GetSiteResponse;
searchText?: string;
communitySearchOptions: Choice[];
creatorSearchOptions: Choice[];
searchCreatorLoading: boolean;
@ -123,6 +124,7 @@ export function getSearchQueryParams(source?: string): SearchProps {
type: getSearchTypeFromQuery,
sort: getSortTypeFromQuery,
listingType: getListingTypeFromQuery,
titleOnly: getBoolFromString,
communityId: getIdFromString,
creatorId: getIdFromString,
page: getPageFromString,
@ -137,8 +139,8 @@ function getSearchTypeFromQuery(type_?: string): SearchType {
return type_ ? (type_ as SearchType) : defaultSearchType;
}
function getSortTypeFromQuery(sort?: string): SortType {
return sort ? (sort as SortType) : defaultSortType;
function getSortTypeFromQuery(sort?: string): PostSortType {
return sort ? (sort as PostSortType) : defaultSortType;
}
function getListingTypeFromQuery(listingType?: string): ListingType {
@ -284,10 +286,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
this.handleCommunityFilterChange =
this.handleCommunityFilterChange.bind(this);
this.handleCreatorFilterChange = this.handleCreatorFilterChange.bind(this);
const { q } = this.props;
this.state.searchText = q;
this.handleTitleOnlyChange = this.handleTitleOnlyChange.bind(this);
// Only fetch the data if coming from another route
if (FirstLoadService.isFirstLoad) {
@ -329,81 +328,142 @@ export class Search extends Component<SearchRouteProps, SearchState> {
}
}
async componentDidMount() {
if (this.props.history.action !== "POP") {
componentWillMount() {
if (!this.state.isIsomorphic && isBrowser()) {
this.fetchAll(this.props);
}
}
componentDidMount() {
if (this.props.history.action !== "POP" || this.state.isIsomorphic) {
this.searchInput.current?.select();
}
}
if (!this.state.isIsomorphic) {
this.setState({
searchCommunitiesLoading: true,
searchCreatorLoading: true,
});
componentWillReceiveProps(nextProps: SearchRouteProps) {
if (nextProps.communityId !== this.props.communityId) {
this.fetchSelectedCommunity(nextProps);
}
if (nextProps.creatorId !== this.props.creatorId) {
this.fetchSelectedCreator(nextProps);
}
this.search(nextProps);
}
const promises = [
HttpService.client
.listCommunities({
type_: defaultListingType,
sort: defaultSortType,
limit: fetchLimit,
})
.then(res => {
if (res.state === "success") {
this.setState({
communitySearchOptions:
res.data.communities.map(communityToChoice),
});
}
}),
];
componentDidUpdate(prevProps: SearchRouteProps) {
if (this.props.location.key !== prevProps.location.key) {
if (this.props.history.action !== "POP") {
this.searchInput.current?.select();
}
}
}
const { communityId, creatorId } = this.props;
fetchDefaultCommunitiesToken?: symbol;
async fetchDefaultCommunities({
communityId,
}: Pick<SearchRouteProps, "communityId">) {
const token = (this.fetchDefaultCommunitiesToken = Symbol());
this.setState({
searchCommunitiesLoading: true,
});
if (communityId) {
promises.push(
HttpService.client.getCommunity({ id: communityId }).then(res => {
if (res.state === "success") {
this.setState(prev => {
prev.communitySearchOptions.unshift(
communityToChoice(res.data.community_view),
);
const res = await HttpService.client.listCommunities({
type_: defaultListingType,
sort: defaultSortType,
limit: fetchLimit,
});
return prev;
});
}
}),
if (token !== this.fetchDefaultCommunitiesToken) {
return;
}
if (res.state === "success") {
const retainSelected: false | undefined | Choice =
!res.data.communities.some(cv => cv.community.id === communityId) &&
this.state.communitySearchOptions.find(
choice => choice.value === communityId?.toString(),
);
}
if (creatorId) {
promises.push(
HttpService.client
.getPersonDetails({
person_id: creatorId,
})
.then(res => {
if (res.state === "success") {
this.setState(prev => {
prev.creatorSearchOptions.push(
personToChoice(res.data.person_view),
);
});
}
}),
);
}
if (this.state.searchText) {
promises.push(this.search());
}
await Promise.all(promises);
const choices = res.data.communities.map(communityToChoice);
this.setState({
searchCommunitiesLoading: false,
searchCreatorLoading: false,
communitySearchOptions: retainSelected
? [retainSelected, ...choices]
: choices,
});
}
this.setState({
searchCommunitiesLoading: false,
});
}
fetchSelectedCommunityToken?: symbol;
async fetchSelectedCommunity({
communityId,
}: Pick<SearchRouteProps, "communityId">) {
const token = (this.fetchSelectedCommunityToken = Symbol());
const needsSelectedCommunity = () => {
return !this.state.communitySearchOptions.some(
choice => choice.value === communityId?.toString(),
);
};
if (communityId && needsSelectedCommunity()) {
const res = await HttpService.client.getCommunity({ id: communityId });
if (
res.state === "success" &&
needsSelectedCommunity() &&
token === this.fetchSelectedCommunityToken
) {
this.setState(prev => {
prev.communitySearchOptions.unshift(
communityToChoice(res.data.community_view),
);
return prev;
});
}
}
}
fetchSelectedCreatorToken?: symbol;
async fetchSelectedCreator({
creatorId,
}: Pick<SearchRouteProps, "creatorId">) {
const token = (this.fetchSelectedCreatorToken = Symbol());
const needsSelectedCreator = () => {
return !this.state.creatorSearchOptions.some(
choice => choice.value === creatorId?.toString(),
);
};
if (!creatorId || !needsSelectedCreator()) {
return;
}
this.setState({ searchCreatorLoading: true });
const res = await HttpService.client.getPersonDetails({
person_id: creatorId,
});
if (token !== this.fetchSelectedCreatorToken) {
return;
}
if (res.state === "success" && needsSelectedCreator()) {
this.setState(prev => {
prev.creatorSearchOptions.push(personToChoice(res.data.person_view));
});
}
this.setState({ searchCreatorLoading: false });
}
async fetchAll(props: SearchRouteProps) {
await Promise.all([
this.fetchDefaultCommunities(props),
this.fetchSelectedCommunity(props),
this.fetchSelectedCreator(props),
this.search(props),
]);
}
static async fetchInitialData({
@ -413,6 +473,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
type: searchType,
sort,
listingType: listing_type,
titleOnly: title_only,
communityId: community_id,
creatorId: creator_id,
page,
@ -458,6 +519,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
type_: searchType,
sort,
listing_type,
title_only,
page,
limit: fetchLimit,
};
@ -533,7 +595,6 @@ export class Search extends Component<SearchRouteProps, SearchState> {
case "Comments":
return this.comments;
case "Posts":
case "Url":
return this.posts;
case "Communities":
return this.communities;
@ -551,13 +612,15 @@ export class Search extends Component<SearchRouteProps, SearchState> {
onSubmit={linkEvent(this, this.handleSearchSubmit)}
>
<div className="col-auto flex-grow-1 flex-sm-grow-0">
{/* key is necessary for defaultValue to update when props.q changes,
e.g. back button. */}
<input
key={this.context.router.history.location.key}
type="text"
className="form-control me-2 mb-2 col-sm-8"
value={this.state.searchText}
defaultValue={this.props.q ?? ""}
placeholder={`${I18NextService.i18n.t("search")}...`}
aria-label={I18NextService.i18n.t("search")}
onInput={linkEvent(this, this.handleQChange)}
required
minLength={1}
ref={this.searchInput}
@ -577,7 +640,8 @@ export class Search extends Component<SearchRouteProps, SearchState> {
}
get selects() {
const { type, listingType, sort, communityId, creatorId } = this.props;
const { type, listingType, titleOnly, sort, communityId, creatorId } =
this.props;
const {
communitySearchOptions,
creatorSearchOptions,
@ -615,6 +679,20 @@ export class Search extends Component<SearchRouteProps, SearchState> {
onChange={this.handleListingTypeChange}
/>
</div>
{(type === "All" || type === "Posts") && (
<div className="col">
<input
className="btn-check"
id="title-only"
type="checkbox"
checked={titleOnly}
onChange={this.handleTitleOnlyChange}
/>
<label className="btn btn-outline-secondary" htmlFor="title-only">
{I18NextService.i18n.t("post_title_only")}
</label>
</div>
)}
<div className="col">
<SortSelect
sort={sort}
@ -766,7 +844,6 @@ export class Search extends Component<SearchRouteProps, SearchState> {
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
// All of these are unused, since its viewonly
finished={new Map()}
onSaveComment={async () => {}}
onBlockPerson={async () => {}}
onDeleteComment={async () => {}}
@ -828,7 +905,6 @@ export class Search extends Component<SearchRouteProps, SearchState> {
allLanguages={siteRes.all_languages}
siteLanguages={siteRes.discussion_languages}
// All of these are unused, since its viewonly
finished={new Map()}
onSaveComment={async () => {}}
onBlockPerson={async () => {}}
onDeleteComment={async () => {}}
@ -984,34 +1060,49 @@ export class Search extends Component<SearchRouteProps, SearchState> {
return resObjCount + searchCount;
}
async search() {
const { searchText: q } = this.state;
const { communityId, creatorId, type, sort, listingType, page } =
this.props;
searchToken?: symbol;
async search(props: SearchRouteProps) {
const token = (this.searchToken = Symbol());
const {
q,
communityId,
creatorId,
type,
sort,
listingType,
titleOnly,
page,
} = props;
if (q) {
this.setState({ searchRes: LOADING_REQUEST });
this.setState({
searchRes: await HttpService.client.search({
q,
community_id: communityId ?? undefined,
creator_id: creatorId ?? undefined,
type_: type,
sort,
listing_type: listingType,
page,
limit: fetchLimit,
}),
const searchRes = await HttpService.client.search({
q,
community_id: communityId ?? undefined,
creator_id: creatorId ?? undefined,
type_: type,
sort,
listing_type: listingType,
title_only: titleOnly,
page,
limit: fetchLimit,
});
if (token !== this.searchToken) {
return;
}
this.setState({ searchRes });
if (myAuth()) {
this.setState({ resolveObjectRes: LOADING_REQUEST });
this.setState({
resolveObjectRes: await HttpService.client.resolveObject({
q,
}),
const resolveObjectRes = await HttpService.client.resolveObject({
q,
});
if (token === this.searchToken) {
this.setState({ resolveObjectRes });
}
}
} else {
this.setState({ searchRes: EMPTY_REQUEST });
}
}
@ -1053,8 +1144,17 @@ export class Search extends Component<SearchRouteProps, SearchState> {
}
});
handleSortChange(sort: SortType) {
this.updateUrl({ sort, page: 1 });
getQ(): string | undefined {
return this.searchInput.current?.value ?? this.props.q;
}
handleSortChange(sort: PostSortType) {
this.updateUrl({ sort, page: 1, q: this.getQ() });
}
handleTitleOnlyChange(event: any) {
const titleOnly = event.target.checked;
this.updateUrl({ titleOnly, q: this.getQ() });
}
handleTypeChange(i: Search, event: any) {
@ -1063,6 +1163,7 @@ export class Search extends Component<SearchRouteProps, SearchState> {
i.updateUrl({
type,
page: 1,
q: i.getQ(),
});
}
@ -1074,20 +1175,23 @@ export class Search extends Component<SearchRouteProps, SearchState> {
this.updateUrl({
listingType,
page: 1,
q: this.getQ(),
});
}
handleCommunityFilterChange({ value }: Choice) {
this.updateUrl({
communityId: getIdFromString(value) ?? 0,
communityId: getIdFromString(value),
page: 1,
q: this.getQ(),
});
}
handleCreatorFilterChange({ value }: Choice) {
this.updateUrl({
creatorId: getIdFromString(value) ?? 0,
creatorId: getIdFromString(value),
page: 1,
q: this.getQ(),
});
}
@ -1095,44 +1199,35 @@ export class Search extends Component<SearchRouteProps, SearchState> {
event.preventDefault();
i.updateUrl({
q: i.state.searchText,
q: i.getQ(),
page: 1,
});
}
handleQChange(i: Search, event: any) {
i.setState({ searchText: event.target.value });
}
async updateUrl({
q,
type,
listingType,
sort,
communityId,
creatorId,
page,
}: Partial<SearchProps>) {
async updateUrl(props: Partial<SearchProps>) {
const {
q: urlQ,
type: urlType,
listingType: urlListingType,
communityId: urlCommunityId,
sort: urlSort,
creatorId: urlCreatorId,
page: urlPage,
} = this.props;
const query = q ?? this.state.searchText ?? urlQ;
q,
type,
listingType,
titleOnly,
sort,
communityId,
creatorId,
page,
} = {
...this.props,
...props,
};
const queryParams: QueryParams<SearchProps> = {
q: query,
type: type ?? urlType,
listingType: listingType ?? urlListingType,
communityId: getUpdatedSearchId(communityId, urlCommunityId),
creatorId: getUpdatedSearchId(creatorId, urlCreatorId),
page: (page ?? urlPage).toString(),
sort: sort ?? urlSort,
q,
type: type,
listingType: listingType,
titleOnly: titleOnly?.toString(),
communityId: communityId?.toString(),
creatorId: creatorId?.toString(),
page: page?.toString(),
sort: sort,
};
this.props.history.push(`/search${getQueryString(queryParams)}`);

View file

@ -9,6 +9,7 @@ export const donateLemmyUrl = `${joinLemmyUrl}/donate`;
export const docsUrl = `${joinLemmyUrl}/docs/en/index.html`;
export const helpGuideUrl = `${joinLemmyUrl}/docs/en/users/01-getting-started.html`; // TODO find a way to redirect to the non-en folder
export const markdownHelpUrl = `${joinLemmyUrl}/docs/en/users/02-media.html`;
export const torrentHelpUrl = `${markdownHelpUrl}#torrents`;
export const sortingHelpUrl = `${joinLemmyUrl}/docs/en/users/03-votes-and-ranking.html`;
export const archiveTodayUrl = "https://archive.today";
export const ghostArchiveUrl = "https://ghostarchive.org";
@ -16,7 +17,6 @@ export const webArchiveUrl = "https://web.archive.org";
export const elementUrl = "https://element.io";
export const postRefetchSeconds: number = 60 * 1000;
export const trendingFetchLimit = 6;
export const mentionDropdownFetchLimit = 10;
export const commentTreeMaxDepth = 8;
export const postMarkdownFieldCharacterLimit = 50000;
@ -25,6 +25,7 @@ export const maxUploadImages = 20;
export const concurrentImageUpload = 4;
export const updateUnreadCountsInterval = 30000;
export const fetchLimit = 20;
export const similarPostFetchLimit = 6;
export const relTags = "noopener nofollow";
export const emDash = "\u2014";
export const authCookieName = "jwt";
@ -49,3 +50,6 @@ export const instanceLinkRegex = new RegExp(
);
export const testHost = "0.0.0.0:8536";
export const validActorRegexPattern =
"^\\w+|[\\p{Script=Arabic}\\d_]+|[\\p{Script=Cyrillic}\\d_]+$";

View file

@ -46,6 +46,11 @@ export interface PostFormParams {
name?: string;
url?: string;
body?: string;
nsfw?: boolean;
language_id?: number;
community_id?: number;
custom_thumbnail?: string;
alt_text?: string;
}
export enum CommentViewType {

View file

@ -1,5 +1,4 @@
import { communitySearch, personSearch } from "@utils/app";
import { isBrowser } from "@utils/browser";
import { debounce, groupBy } from "@utils/helpers";
import { CommunityTribute, PersonTribute } from "@utils/types";
import { Picker } from "emoji-mart";
@ -15,12 +14,13 @@ import markdown_it_ruby from "markdown-it-ruby";
import markdown_it_sub from "markdown-it-sub";
import markdown_it_sup from "markdown-it-sup";
import markdown_it_highlightjs from "markdown-it-highlightjs/core";
import Renderer from "markdown-it/lib/renderer";
import Token from "markdown-it/lib/token";
import { Renderer, Token } from "markdown-it";
import { instanceLinkRegex, relTags } from "./config";
import { lazyHighlightjs } from "./lazy-highlightjs";
import { HttpService } from "./services";
import { WrappedLemmyHttp } from "./services/HttpService";
export let Tribute: any;
let Tribute: any;
export let md: MarkdownIt = new MarkdownIt();
@ -34,17 +34,13 @@ export const mdLimited: MarkdownIt = new MarkdownIt("zero").enable([
"strikethrough",
]);
export const customEmojis: EmojiMartCategory[] = [];
let customEmojis: EmojiMartCategory[] = [];
export let customEmojisLookup: Map<string, CustomEmojiView> = new Map<
string,
CustomEmojiView
>();
if (isBrowser()) {
Tribute = require("tributejs");
}
export function mdToHtml(text: string, rerender: () => void) {
return { __html: lazyHighlightjs.render(md, text, rerender) };
}
@ -210,14 +206,17 @@ export function setupMarkdown() {
) {
//Provide custom renderer for our emojis to allow us to add a css class and force size dimensions on them.
const item = tokens[idx] as any;
let title = item.attrs.length >= 3 ? item.attrs[2][1] : "";
const url = item.attrs.length > 0 ? item.attrs[0][1] : "";
const altText = item.attrs.length > 1 ? item.attrs[1][1] : "";
const title = item.attrs.length > 2 ? item.attrs[2][1] : "";
const splitTitle = title.split(/ (.*)/, 2);
const isEmoji = splitTitle[0] === "emoji";
let shortcode: string | undefined;
if (isEmoji) {
title = splitTitle[1];
shortcode = splitTitle[1];
}
const customEmoji = customEmojisLookup.get(title);
const isLocalEmoji = customEmoji !== undefined;
// customEmojisLookup is empty in SSR, CSR rerenders markdown anyway
const isLocalEmoji = shortcode && customEmojisLookup.has(shortcode);
if (!isLocalEmoji) {
const imgElement =
defaultImageRenderer?.(tokens, idx, options, env, self) ?? "";
@ -228,10 +227,8 @@ export function setupMarkdown() {
} else return "";
}
return `<img class="icon icon-emoji" src="${
customEmoji!.custom_emoji.image_url
}" title="${customEmoji!.custom_emoji.shortcode}" alt="${
customEmoji!.custom_emoji.alt_text
}"/>`;
url
}" title="${shortcode}" alt="${altText}"/>`;
};
md.renderer.rules.table_open = function () {
return '<table class="table">';
@ -253,11 +250,14 @@ export function setupMarkdown() {
};
}
export function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
export function emojiMartCategories(
custom_emoji_views: CustomEmojiView[],
): EmojiMartCategory[] {
const groupedEmojis = groupBy(
custom_emoji_views,
x => x.custom_emoji.category,
);
const customEmojis: EmojiMartCategory[] = [];
for (const [category, emojis] of Object.entries(groupedEmojis)) {
customEmojis.push({
id: category,
@ -270,63 +270,24 @@ export function setupEmojiDataModel(custom_emoji_views: CustomEmojiView[]) {
})),
});
}
return customEmojis;
}
export async function setupEmojiDataModel(
client: WrappedLemmyHttp = HttpService.client,
): Promise<boolean> {
const emojisRes = await client.listCustomEmojis({
ignore_page_limits: true,
});
if (emojisRes.state !== "success") {
return false;
}
const custom_emoji_views = emojisRes.data.custom_emojis;
customEmojis = emojiMartCategories(custom_emoji_views);
customEmojisLookup = new Map(
custom_emoji_views.map(view => [view.custom_emoji.shortcode, view]),
);
}
export function updateEmojiDataModel(custom_emoji_view: CustomEmojiView) {
const emoji: EmojiMartCustomEmoji = {
id: custom_emoji_view.custom_emoji.shortcode,
name: custom_emoji_view.custom_emoji.shortcode,
keywords: custom_emoji_view.keywords.map(x => x.keyword),
skins: [{ src: custom_emoji_view.custom_emoji.image_url }],
};
const categoryIndex = customEmojis.findIndex(
x => x.id === custom_emoji_view.custom_emoji.category,
);
if (categoryIndex === -1) {
customEmojis.push({
id: custom_emoji_view.custom_emoji.category,
name: custom_emoji_view.custom_emoji.category,
emojis: [emoji],
});
} else {
const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
x => x.id === custom_emoji_view.custom_emoji.shortcode,
);
if (emojiIndex === -1) {
customEmojis[categoryIndex].emojis.push(emoji);
} else {
customEmojis[categoryIndex].emojis[emojiIndex] = emoji;
}
}
customEmojisLookup.set(
custom_emoji_view.custom_emoji.shortcode,
custom_emoji_view,
);
}
export function removeFromEmojiDataModel(id: number) {
let view: CustomEmojiView | undefined;
for (const item of customEmojisLookup.values()) {
if (item.custom_emoji.id === id) {
view = item;
break;
}
}
if (!view) return;
const categoryIndex = customEmojis.findIndex(
x => x.id === view?.custom_emoji.category,
);
const emojiIndex = customEmojis[categoryIndex].emojis.findIndex(
x => x.id === view?.custom_emoji.shortcode,
);
customEmojis[categoryIndex].emojis = customEmojis[
categoryIndex
].emojis.splice(emojiIndex, 1);
customEmojisLookup.delete(view?.custom_emoji.shortcode);
return true;
}
export function getEmojiMart(
@ -334,14 +295,20 @@ export function getEmojiMart(
customPickerOptions: any = {},
) {
const pickerOptions = {
...customPickerOptions,
onEmojiSelect: onEmojiSelect,
custom: customEmojis,
...customPickerOptions,
};
return new Picker(pickerOptions);
}
export function setupTribute() {
export async function setupTribute() {
// eslint-disable-next-line eqeqeq
if (Tribute == null) {
console.debug("Tribute is null, importing...");
Tribute = (await import("tributejs")).default;
}
return new Tribute({
noMatchTemplate: function () {
return "";
@ -415,7 +382,7 @@ export function setupTribute() {
});
}
interface EmojiMartCategory {
export interface EmojiMartCategory {
id: string;
name: string;
emojis: EmojiMartCustomEmoji[];

View file

@ -28,7 +28,11 @@ import {
} from "./components/home/login";
import { LoginReset } from "./components/home/login-reset";
import { Setup } from "./components/home/setup";
import { Signup } from "./components/home/signup";
import {
Signup,
SignupFetchConfig,
getSignupQueryParams,
} from "./components/home/signup";
import {
Modlog,
ModlogFetchConfig,
@ -53,7 +57,11 @@ import {
CreatePost,
getCreatePostQueryParams,
} from "./components/post/create-post";
import { Post, PostFetchConfig } from "./components/post/post";
import {
Post,
PostFetchConfig,
getPostQueryParams,
} from "./components/post/post";
import {
CreatePrivateMessage,
CreatePrivateMessageFetchConfig,
@ -71,6 +79,11 @@ import {
import { InitialFetchRequest, RouteData } from "./interfaces";
import { GetSiteResponse } from "lemmy-js-client";
import { Inferno } from "inferno";
import {
OAuthCallback,
OAuthCallbackConfig,
getOAuthCallbackQueryParams,
} from "./components/home/oauth/oauth-callback";
export interface IRoutePropsWithFetch<
DataT extends RouteData,
@ -87,6 +100,7 @@ export interface IRoutePropsWithFetch<
component: Inferno.ComponentClass<
RouteComponentProps<PathPropsT> & QueryPropsT
>;
mountedSameRouteNavKey?: string;
}
export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
@ -96,6 +110,7 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
fetchInitialData: Home.fetchInitialData,
exact: true,
getQueryParams: getHomeQueryParams,
mountedSameRouteNavKey: "home",
} as HomeFetchConfig,
{
path: `/login`,
@ -108,13 +123,15 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
},
{
path: `/signup`,
getQueryParams: getSignupQueryParams,
component: Signup,
},
} as SignupFetchConfig,
{
path: `/create_post`,
component: CreatePost,
fetchInitialData: CreatePost.fetchInitialData,
getQueryParams: getCreatePostQueryParams,
mountedSameRouteNavKey: "create_post",
} as CreatePostFetchConfig,
{
path: `/create_community`,
@ -130,28 +147,38 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
component: Communities,
fetchInitialData: Communities.fetchInitialData,
getQueryParams: getCommunitiesQueryParams,
mountedSameRouteNavKey: "communities",
} as CommunitiesFetchConfig,
{
path: `/post/:post_id`,
// "/comment/:post_id?/:comment_id" would be preferable as direct comment
// link, but it looks like a Route can't match multiple paths and a
// component can't stay mounted across routes.
path: `/post/:post_id/:comment_id?`,
component: Post,
fetchInitialData: Post.fetchInitialData,
getQueryParams: getPostQueryParams,
mountedSameRouteNavKey: "post",
} as PostFetchConfig,
{
path: `/comment/:comment_id`,
component: Post,
fetchInitialData: Post.fetchInitialData,
getQueryParams: getPostQueryParams,
mountedSameRouteNavKey: "post",
} as PostFetchConfig,
{
path: `/c/:name`,
component: Community,
fetchInitialData: Community.fetchInitialData,
getQueryParams: getCommunityQueryParams,
mountedSameRouteNavKey: "community",
} as CommunityFetchConfig,
{
path: `/u/:username`,
component: Profile,
fetchInitialData: Profile.fetchInitialData,
getQueryParams: getProfileQueryParams,
mountedSameRouteNavKey: "profile",
} as ProfileFetchConfig,
{
path: `/inbox`,
@ -164,16 +191,11 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
fetchInitialData: Settings.fetchInitialData,
} as SettingsFetchConfig,
{
path: `/modlog/:communityId`,
component: Modlog,
fetchInitialData: Modlog.fetchInitialData,
getQueryParams: getModlogQueryParams,
} as ModlogFetchConfig,
{
path: `/modlog`,
path: `/modlog/:communityId?`,
component: Modlog,
fetchInitialData: Modlog.fetchInitialData,
getQueryParams: getModlogQueryParams,
mountedSameRouteNavKey: "modlog",
} as ModlogFetchConfig,
{ path: `/setup`, component: Setup },
{
@ -196,6 +218,7 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
component: Search,
fetchInitialData: Search.fetchInitialData,
getQueryParams: getSearchQueryParams,
mountedSameRouteNavKey: "search",
} as SearchFetchConfig,
{
path: `/password_change/:token`,
@ -205,6 +228,11 @@ export const routes: IRoutePropsWithFetch<RouteData, any, any>[] = [
path: `/verify_email/:token`,
component: VerifyEmail,
},
{
path: `/oauth/callback`,
getQueryParams: getOAuthCallbackQueryParams,
component: OAuthCallback,
} as OAuthCallbackConfig,
{
path: `/instances`,
component: Instances,

View file

@ -35,6 +35,7 @@ export const languages: TranslationDesc[] = [
{ resource: "ja", code: "ja", name: "日本語" },
{ resource: "ko", code: "ko", name: "한국어" },
{ resource: "nl", code: "nl", name: "Nederlands" },
{ resource: "nn", code: "nn", name: "nynorsk" },
{ resource: "oc", code: "oc", name: "Occitan" },
{ resource: "pl", code: "pl", name: "Polski" },
{ resource: "pt", code: "pt", name: "Português" },

Some files were not shown because too many files have changed in this diff Show more