kopia lustrzana https://github.com/nolanlawson/pinafore
				
				
				
			implement search
							rodzic
							
								
									eb5b6999ce
								
							
						
					
					
						commit
						8761a46767
					
				|  | @ -18,4 +18,5 @@ module.exports = [ | ||||||
|   {id:'fa-user-times', src:'node_modules/font-awesome-svg-png/white/svg/user-times.svg', title: 'Stop Following'}, |   {id:'fa-user-times', src:'node_modules/font-awesome-svg-png/white/svg/user-times.svg', title: 'Stop Following'}, | ||||||
|   {id:'fa-user-plus', src:'node_modules/font-awesome-svg-png/white/svg/user-plus.svg', title: 'Follow'}, |   {id:'fa-user-plus', src:'node_modules/font-awesome-svg-png/white/svg/user-plus.svg', title: 'Follow'}, | ||||||
|   {id:'fa-external-link', src:'node_modules/font-awesome-svg-png/white/svg/external-link.svg', title: 'External Link'}, |   {id:'fa-external-link', src:'node_modules/font-awesome-svg-png/white/svg/external-link.svg', title: 'External Link'}, | ||||||
|  |   {id:'fa-search', src:'node_modules/font-awesome-svg-png/white/svg/search.svg', title: 'Search'}, | ||||||
| ] | ] | ||||||
|  | @ -0,0 +1,24 @@ | ||||||
|  | <div class="loading-page"> | ||||||
|  |   <LoadingSpinner /> | ||||||
|  | </div> | ||||||
|  | <style> | ||||||
|  |   .loading-page { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 0; | ||||||
|  |     left: 0; | ||||||
|  |     right: 0; | ||||||
|  |     height: 150px; | ||||||
|  |     display: flex; | ||||||
|  |     align-items: center; | ||||||
|  |     justify-content: center; | ||||||
|  |     z-index: 50; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import LoadingSpinner from './LoadingSpinner.html' | ||||||
|  |   export default { | ||||||
|  |     components: { | ||||||
|  |       LoadingSpinner | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -12,6 +12,9 @@ | ||||||
| 		<li> | 		<li> | ||||||
|       <NavItem :page name="federated" href="/federated" svg="#fa-globe" label="Federated" /> |       <NavItem :page name="federated" href="/federated" svg="#fa-globe" label="Federated" /> | ||||||
| 		</li> | 		</li> | ||||||
|  |     <li> | ||||||
|  |       <NavItem :page name="search" href="/search" svg="#fa-search" label="Search" /> | ||||||
|  |     </li> | ||||||
| 	  <li> | 	  <li> | ||||||
|       <NavItem :page name="settings" href="/settings" svg="#fa-gear" label="Settings" /> |       <NavItem :page name="settings" href="/settings" svg="#fa-gear" label="Settings" /> | ||||||
| 		</li> | 		</li> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,49 @@ | ||||||
|  | <SearchResult href="/accounts/{{account.id}}"> | ||||||
|  |   <div class="search-result-account"> | ||||||
|  |     <Avatar :account size="small" className="search-result-account-avatar"/> | ||||||
|  |     <div class="search-result-account-name"> | ||||||
|  |       {{account.display_name}} | ||||||
|  |     </div> | ||||||
|  |     <div class="search-result-account-username"> | ||||||
|  |       {{'@' + account.acct}} | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </SearchResult> | ||||||
|  | <style> | ||||||
|  |   .search-result-account { | ||||||
|  |     display: grid; | ||||||
|  |     grid-template-areas: | ||||||
|  |       "avatar    name" | ||||||
|  |       "avatar    username"; | ||||||
|  |     grid-column-gap: 20px; | ||||||
|  |     grid-template-columns: max-content 1fr; | ||||||
|  |     align-items: center; | ||||||
|  |   } | ||||||
|  |   :global(.search-result-account-avatar) { | ||||||
|  |     grid-area: avatar; | ||||||
|  |   } | ||||||
|  |   .search-result-account-name { | ||||||
|  |     grid-area: name; | ||||||
|  |     white-space: nowrap; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-overflow: ellipsis; | ||||||
|  |     font-size: 1.2em; | ||||||
|  |   } | ||||||
|  |   .search-result-account-username { | ||||||
|  |     grid-area: username; | ||||||
|  |     white-space: nowrap; | ||||||
|  |     overflow: hidden; | ||||||
|  |     text-overflow: ellipsis; | ||||||
|  |     color: var(--deemphasized-text-color); | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import Avatar from '../status/Avatar.html' | ||||||
|  |   import SearchResult from './SearchResult.html' | ||||||
|  |   export default { | ||||||
|  |     components: { | ||||||
|  |       Avatar, | ||||||
|  |       SearchResult | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | <SearchResult href="/tags/{{hashtag}}"> | ||||||
|  |   {{'#' + hashtag}} | ||||||
|  | </SearchResult> | ||||||
|  | <style> | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import SearchResult from './SearchResult.html' | ||||||
|  |   export default { | ||||||
|  |     components: { | ||||||
|  |       SearchResult | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -0,0 +1,93 @@ | ||||||
|  | <form class="search-input-form" on:submit="onSubmit(event)"> | ||||||
|  |   <div class="search-input-wrapper"> | ||||||
|  |     <input type="search" | ||||||
|  |            class="search-input" | ||||||
|  |            placeholder="Search" | ||||||
|  |            aria-label="Search input" | ||||||
|  |            required | ||||||
|  |            bind:value="$queryInSearch"> | ||||||
|  |   </div> | ||||||
|  |   <button type="submit" class="primary search-button" aria-label="Search"> | ||||||
|  |     <svg> | ||||||
|  |       <use xlink:href="#fa-search" /> | ||||||
|  |     </svg> | ||||||
|  |   </button> | ||||||
|  | </form> | ||||||
|  | {{#if loading}} | ||||||
|  |   <div class="search-results-container"> | ||||||
|  |     <LoadingPage /> | ||||||
|  |   </div> | ||||||
|  | {{elseif $searchResults && $searchResultsForQuery === $queryInSearch}} | ||||||
|  |   <div class="search-results-container"> | ||||||
|  |     <SearchResults /> | ||||||
|  |   </div> | ||||||
|  | {{/if}} | ||||||
|  | <style> | ||||||
|  |   .search-input-form { | ||||||
|  |     display: grid; | ||||||
|  |     grid-template-columns: 1fr min-content; | ||||||
|  |     grid-gap: 10px; | ||||||
|  |   } | ||||||
|  |   .search-input-wrapper { | ||||||
|  |     position: relative; | ||||||
|  |     display: flex; | ||||||
|  |     justify-content: center; | ||||||
|  |     align-items: center; | ||||||
|  |   } | ||||||
|  |   .search-input { | ||||||
|  |     padding: 10px 15px; | ||||||
|  |     border-radius: 10px; | ||||||
|  |     flex: 1; | ||||||
|  |   } | ||||||
|  |   .search-button svg { | ||||||
|  |     fill: var(--button-primary-text); | ||||||
|  |     width: 18px; | ||||||
|  |     height: 18px; | ||||||
|  |     flex: 1; | ||||||
|  |   } | ||||||
|  |   .search-results-container { | ||||||
|  |     position: relative; | ||||||
|  |     margin-top: 20px; | ||||||
|  |   } | ||||||
|  |   @media (min-width: 768px) { | ||||||
|  |     .search-button { | ||||||
|  |       min-width: 100px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import { store } from '../../_store/store' | ||||||
|  |   import LoadingPage from '../LoadingPage.html' | ||||||
|  |   import { toast } from '../../_utils/toast' | ||||||
|  |   import { search } from '../../_utils/mastodon/search' | ||||||
|  |   import SearchResults from './SearchResults.html' | ||||||
|  | 
 | ||||||
|  |   export default { | ||||||
|  |     store: () => store, | ||||||
|  |     components: { | ||||||
|  |       LoadingPage, | ||||||
|  |       SearchResults | ||||||
|  |     }, | ||||||
|  |     methods: { | ||||||
|  |       async onSubmit (e) { | ||||||
|  |         e.preventDefault() | ||||||
|  |         let instanceName = this.store.get('currentInstance') | ||||||
|  |         let accessToken = this.store.get('accessToken') | ||||||
|  |         let queryInSearch = this.store.get('queryInSearch') | ||||||
|  |         this.set({loading: true}) | ||||||
|  |         try { | ||||||
|  |           let results = await search(instanceName, accessToken, queryInSearch) | ||||||
|  |           this.store.set({ | ||||||
|  |             searchResultsForQuery: queryInSearch, | ||||||
|  |             searchResults: results | ||||||
|  |           }) | ||||||
|  |         } catch (e) { | ||||||
|  |           toast.say('Error during search: ' + (e.name || '') + ' ' + (e.message || '')) | ||||||
|  |           console.error(e) | ||||||
|  |         } finally { | ||||||
|  |           this.set({loading: false}) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -0,0 +1,28 @@ | ||||||
|  | <li class="search-result"> | ||||||
|  |   <a href="{{href}}" class="search-result-anchor"> | ||||||
|  |     <slot></slot> | ||||||
|  |   </a> | ||||||
|  | </li> | ||||||
|  | <style> | ||||||
|  |   .search-result { | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     border-bottom: 1px solid var(--main-border); | ||||||
|  |     display: flex; | ||||||
|  |   } | ||||||
|  |   .search-result:last-child { | ||||||
|  |     border-bottom: none; | ||||||
|  |   } | ||||||
|  |   .search-result-anchor { | ||||||
|  |     padding: 20px; | ||||||
|  |     flex: 1; | ||||||
|  |     background: var(--settings-list-item-bg); | ||||||
|  |     color: var(--body-text-color); | ||||||
|  |   } | ||||||
|  |   .search-result-anchor:hover { | ||||||
|  |     background: var(--settings-list-item-bg-hover); | ||||||
|  |     text-decoration: none; | ||||||
|  |   } | ||||||
|  |   .search-result-anchor:active { | ||||||
|  |     background: var(--settings-list-item-bg-active); | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | @ -0,0 +1,34 @@ | ||||||
|  | <ul class="search-results"> | ||||||
|  |   {{#each $searchResults.hashtags as hashtag}} | ||||||
|  |     <HashtagSearchResult :hashtag /> | ||||||
|  |   {{/each}} | ||||||
|  |   {{#each $searchResults.accounts as account}} | ||||||
|  |     <AccountSearchResult :account /> | ||||||
|  |   {{/each}} | ||||||
|  |   {{#each $searchResults.statuses as status, index}} | ||||||
|  |   <StatusSearchResult :status :index length="{{$searchResults.statuses.length}}"/> | ||||||
|  |   {{/each}} | ||||||
|  | </ul> | ||||||
|  | <style> | ||||||
|  |   .search-results { | ||||||
|  |     list-style: none; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     border: 1px solid var(--main-border); | ||||||
|  |     border-radius: 2px; | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import { store } from '../../_store/store' | ||||||
|  |   import AccountSearchResult from './AccountSearchResult.html' | ||||||
|  |   import HashtagSearchResult from './HashtagSearchResult.html' | ||||||
|  |   import StatusSearchResult from './StatusSearchResult.html' | ||||||
|  | 
 | ||||||
|  |   export default { | ||||||
|  |     store: () => store, | ||||||
|  |     components: { | ||||||
|  |       AccountSearchResult, | ||||||
|  |       HashtagSearchResult, | ||||||
|  |       StatusSearchResult | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -0,0 +1,18 @@ | ||||||
|  | <SearchResult href="/statuses/{{status.id}}"> | ||||||
|  |   <Status :index :length | ||||||
|  |           timelineType="search" timelineValue="search" | ||||||
|  |           status="{{status}}" /> | ||||||
|  | </SearchResult> | ||||||
|  | <style> | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import SearchResult from './SearchResult.html' | ||||||
|  |   import Status from '../status/Status.html' | ||||||
|  | 
 | ||||||
|  |   export default { | ||||||
|  |     components: { | ||||||
|  |       SearchResult, | ||||||
|  |       Status | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| {{#if error}} | {{#if error}} | ||||||
| <svg class="{{className}} avatar size-{{size}}" aria-hidden="true"> | <svg class="{{className || ''}} avatar size-{{size}}" aria-hidden="true"> | ||||||
|   <use xlink:href="#fa-user" /> |   <use xlink:href="#fa-user" /> | ||||||
| </svg> | </svg> | ||||||
| {{elseif $autoplayGifs}} | {{elseif $autoplayGifs}} | ||||||
|   <img class="{{className}} avatar size-{{size}}" aria-hidden="true" alt="" |   <img class="{{className || ''}} avatar size-{{size}}" aria-hidden="true" alt="" | ||||||
|      src="{{account.avatar}}" on:imgLoadError="set({error: true})" /> |      src="{{account.avatar}}" on:imgLoadError="set({error: true})" /> | ||||||
| {{else}} | {{else}} | ||||||
|   <NonAutoplayImg className="{{className}} avatar size-{{size}}" ariaHidden="true" alt="" |   <NonAutoplayImg className="{{className || ''}} avatar size-{{size}}" ariaHidden="true" alt="" | ||||||
|        src="{{account.avatar}}" staticSrc="{{account.avatar_static}}" on:imgLoadError="set({error: true})" /> |        src="{{account.avatar}}" staticSrc="{{account.avatar_static}}" on:imgLoadError="set({error: true})" /> | ||||||
| {{/if}} | {{/if}} | ||||||
| <style> | <style> | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| <article class="status-article {{originalStatus.visibility === 'direct' ? 'status-direct' : ''}}" | <article class="status-article {{getClasses(originalStatus, timelineType)}}" | ||||||
|          tabindex="0" |          tabindex="0" | ||||||
|          aria-posinset="{{index}}" aria-setsize="{{length}}" |          aria-posinset="{{index}}" aria-setsize="{{length}}" | ||||||
|          on:recalculateHeight> |          on:recalculateHeight> | ||||||
|  | @ -21,10 +21,8 @@ | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
|   .status-article { |   .status-article { | ||||||
|     width: 560px; |  | ||||||
|     max-width: calc(100vw - 40px); |     max-width: calc(100vw - 40px); | ||||||
|     padding: 10px 20px; |     padding: 10px 20px; | ||||||
|     border-bottom: 1px solid var(--main-border); |  | ||||||
|     display: grid; |     display: grid; | ||||||
|     grid-template-areas: |     grid-template-areas: | ||||||
|         ".............. status-header" |         ".............. status-header" | ||||||
|  | @ -37,6 +35,11 @@ | ||||||
|     grid-template-columns: 58px 1fr; |     grid-template-columns: 58px 1fr; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   .status-article.status-in-timeline { | ||||||
|  |     width: 560px; | ||||||
|  |     border-bottom: 1px solid var(--main-border); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   .status-article.status-direct { |   .status-article.status-direct { | ||||||
|     background-color: var(--status-direct-background); |     background-color: var(--status-direct-background); | ||||||
|   } |   } | ||||||
|  | @ -69,6 +72,12 @@ | ||||||
|       StatusSpoiler |       StatusSpoiler | ||||||
|     }, |     }, | ||||||
|     store: () => store, |     store: () => store, | ||||||
|  |     helpers: { | ||||||
|  |       getClasses(originalStatus, timelineType) { | ||||||
|  |         return (originalStatus.visibility === 'direct' ? 'status-direct' : '') + | ||||||
|  |           ' ' + (timelineType !== 'search' ? 'status-in-timeline' : '') | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     computed: { |     computed: { | ||||||
|       originalStatus: (status) => status.reblog ? status.reblog : status, |       originalStatus: (status) => status.reblog ? status.reblog : status, | ||||||
|       statusId: (originalStatus) => originalStatus.id, |       statusId: (originalStatus) => originalStatus.id, | ||||||
|  |  | ||||||
|  | @ -1,8 +1,6 @@ | ||||||
| <div class="timeline" role="feed" aria-label="{{label}}"> | <div class="timeline" role="feed" aria-label="{{label}}"> | ||||||
|   {{#if !$initialized}} |   {{#if !$initialized}} | ||||||
|     <div class="loading-page"> |     <LoadingPage /> | ||||||
|       <LoadingSpinner /> |  | ||||||
|     </div> |  | ||||||
|   {{/if}} |   {{/if}} | ||||||
|   {{#if timelineType === 'notifications'}} |   {{#if timelineType === 'notifications'}} | ||||||
|     <VirtualList component="{{NotificationVirtualListItem}}" |     <VirtualList component="{{NotificationVirtualListItem}}" | ||||||
|  | @ -44,17 +42,6 @@ | ||||||
|     min-height: 60vh; |     min-height: 60vh; | ||||||
|     position: relative; |     position: relative; | ||||||
|   } |   } | ||||||
|   .loading-page { |  | ||||||
|     position: absolute; |  | ||||||
|     top: 0; |  | ||||||
|     left: 0; |  | ||||||
|     right: 0; |  | ||||||
|     height: 150px; |  | ||||||
|     display: flex; |  | ||||||
|     align-items: center; |  | ||||||
|     justify-content: center; |  | ||||||
|     z-index: 50; |  | ||||||
|   } |  | ||||||
| </style> | </style> | ||||||
| <script> | <script> | ||||||
|   import { store } from '../../_store/store' |   import { store } from '../../_store/store' | ||||||
|  | @ -67,7 +54,7 @@ | ||||||
|   import { timelines } from '../../_static/timelines' |   import { timelines } from '../../_static/timelines' | ||||||
|   import { database } from '../../_utils/database/database' |   import { database } from '../../_utils/database/database' | ||||||
|   import { initializeTimeline, fetchTimelineItemsOnScrollToBottom, setupTimeline } from '../../_actions/timeline' |   import { initializeTimeline, fetchTimelineItemsOnScrollToBottom, setupTimeline } from '../../_actions/timeline' | ||||||
|   import LoadingSpinner from '../LoadingSpinner.html' |   import LoadingPage from '../LoadingPage.html' | ||||||
| 
 | 
 | ||||||
|   export default { |   export default { | ||||||
|     async oncreate() { |     async oncreate() { | ||||||
|  | @ -118,7 +105,7 @@ | ||||||
|     components: { |     components: { | ||||||
|       VirtualList, |       VirtualList, | ||||||
|       PseudoVirtualList, |       PseudoVirtualList, | ||||||
|       LoadingSpinner |       LoadingPage | ||||||
|     }, |     }, | ||||||
|     methods: { |     methods: { | ||||||
|       initialize() { |       initialize() { | ||||||
|  |  | ||||||
|  | @ -23,6 +23,7 @@ class PinaforeStore extends LocalStorageStore { | ||||||
| 
 | 
 | ||||||
| const store = new PinaforeStore({ | const store = new PinaforeStore({ | ||||||
|   instanceNameInSearch: '', |   instanceNameInSearch: '', | ||||||
|  |   queryInSearch: '', | ||||||
|   currentInstance: null, |   currentInstance: null, | ||||||
|   loggedInInstances: {}, |   loggedInInstances: {}, | ||||||
|   loggedInInstancesInOrder: [], |   loggedInInstancesInOrder: [], | ||||||
|  |  | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | import { get, paramsString } from '../ajax' | ||||||
|  | 
 | ||||||
|  | export function search(instanceName, accessToken, query) { | ||||||
|  |   let url = `https://${instanceName}/api/v1/search?` + paramsString({ | ||||||
|  |     q: query, | ||||||
|  |     resolve: true | ||||||
|  |   }) | ||||||
|  |   return get(url, { | ||||||
|  |     'Authorization': `Bearer ${accessToken}` | ||||||
|  |   }) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @ -0,0 +1,48 @@ | ||||||
|  | <:Head> | ||||||
|  |   <title>Pinafore – Search</title> | ||||||
|  | </:Head> | ||||||
|  | 
 | ||||||
|  | <Layout page='search'> | ||||||
|  |   {{#if $isUserLoggedIn}} | ||||||
|  |   <div class="search-page"> | ||||||
|  |     <Search></Search> | ||||||
|  |   </div> | ||||||
|  |   {{else}} | ||||||
|  |   <HiddenFromSSR> | ||||||
|  |     <FreeTextLayout> | ||||||
|  |       <h1>Search</h1> | ||||||
|  | 
 | ||||||
|  |       <p>You can search once logged in to an instance.</p> | ||||||
|  |     </FreeTextLayout> | ||||||
|  |   </HiddenFromSSR> | ||||||
|  |   {{/if}} | ||||||
|  | </Layout> | ||||||
|  | <style> | ||||||
|  |   .search-page { | ||||||
|  |     min-height: 60vh; | ||||||
|  |     padding: 20px 20px; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   @media (max-width: 767px) { | ||||||
|  |     .search-page { | ||||||
|  |       padding: 20px 10px; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </style> | ||||||
|  | <script> | ||||||
|  |   import Layout from './_components/Layout.html' | ||||||
|  |   import FreeTextLayout from './_components/FreeTextLayout.html' | ||||||
|  |   import { store } from './_store/store.js' | ||||||
|  |   import HiddenFromSSR from './_components/HiddenFromSSR' | ||||||
|  |   import Search from './_components/search/Search.html' | ||||||
|  | 
 | ||||||
|  |   export default { | ||||||
|  |     store: () => store, | ||||||
|  |     components: { | ||||||
|  |       Layout, | ||||||
|  |       Search, | ||||||
|  |       FreeTextLayout, | ||||||
|  |       HiddenFromSSR | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | </script> | ||||||
|  | @ -129,3 +129,8 @@ button::-moz-focus-inner { | ||||||
|   border: 0; |   border: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | /* Firefox hacks to remove ugly red border. | ||||||
|  |    Unnecessary since it gives a warning if you submit an empty field anyway. */ | ||||||
|  | input:required, input:invalid { | ||||||
|  |   box-shadow: none; | ||||||
|  | } | ||||||
|  | @ -11,7 +11,7 @@ | ||||||
| 	<style> | 	<style> | ||||||
| /* auto-generated w/ build-sass.js */ | /* auto-generated w/ build-sass.js */ | ||||||
| body{--button-primary-bg:#6081e6;--button-primary-text:#fff;--button-primary-border:#132c76;--button-primary-bg-active:#456ce2;--button-primary-bg-hover:#6988e7;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#4169e1;--main-bg:#fff;--body-bg:#e8edfb;--body-text-color:#333;--main-border:#dadada;--svg-fill:#4169e1;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#4169e1;--nav-border:#214cce;--nav-a-border:#4169e1;--nav-a-selected-border:#fff;--nav-a-selected-bg:#6d8ce8;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#839deb;--nav-a-bg-hover:#577ae4;--nav-a-border-hover:#4169e1;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#90a8ee;--action-button-fill-color-hover:#a2b6f0;--action-button-fill-color-active:#577ae4;--action-button-fill-color-pressed:#2351dc;--action-button-fill-color-pressed-hover:#3862e0;--action-button-fill-color-pressed-active:#1d44b8;--settings-list-item-bg:#fff;--settings-list-item-text:#4169e1;--settings-list-item-text-hover:#4169e1;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--mask-opaque-bg:rgba(51,51,51,0.8);--loading-bg:#ededed;--deemphasized-text-color:#666;--focus-outline:#c5d1f6;--very-deemphasized-link-color:rgba(65,105,225,0.6);--very-deemphasized-text-color:rgba(102,102,102,0.6);--status-direct-background:#d2dcf8} | body{--button-primary-bg:#6081e6;--button-primary-text:#fff;--button-primary-border:#132c76;--button-primary-bg-active:#456ce2;--button-primary-bg-hover:#6988e7;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#4169e1;--main-bg:#fff;--body-bg:#e8edfb;--body-text-color:#333;--main-border:#dadada;--svg-fill:#4169e1;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#4169e1;--nav-border:#214cce;--nav-a-border:#4169e1;--nav-a-selected-border:#fff;--nav-a-selected-bg:#6d8ce8;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#839deb;--nav-a-bg-hover:#577ae4;--nav-a-border-hover:#4169e1;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#90a8ee;--action-button-fill-color-hover:#a2b6f0;--action-button-fill-color-active:#577ae4;--action-button-fill-color-pressed:#2351dc;--action-button-fill-color-pressed-hover:#3862e0;--action-button-fill-color-pressed-active:#1d44b8;--settings-list-item-bg:#fff;--settings-list-item-text:#4169e1;--settings-list-item-text-hover:#4169e1;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--mask-opaque-bg:rgba(51,51,51,0.8);--loading-bg:#ededed;--deemphasized-text-color:#666;--focus-outline:#c5d1f6;--very-deemphasized-link-color:rgba(65,105,225,0.6);--very-deemphasized-text-color:rgba(102,102,102,0.6);--status-direct-background:#d2dcf8} | ||||||
| body{margin:0;font-family:system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue;font-size:14px;line-height:1.3;color:var(--body-text-color);background:var(--body-bg);position:fixed;left:0;right:0;bottom:0;top:0}.container{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;will-change:transform;position:absolute;top:72px;left:0;right:0;bottom:0}@media (max-width: 767px){.container{top:62px}}main{position:relative;width:602px;max-width:100vw;padding:0;box-sizing:border-box;margin:30px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px}@media (max-width: 767px){main{margin:5px auto 15px}}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px}button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover{background:var(--button-bg-hover)}button:active{background:var(--button-bg-active)}button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}*:focus{outline:2px solid var(--focus-outline)}button::-moz-focus-inner{border:0} | body{margin:0;font-family:system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue;font-size:14px;line-height:1.3;color:var(--body-text-color);background:var(--body-bg);position:fixed;left:0;right:0;bottom:0;top:0}.container{overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;will-change:transform;position:absolute;top:72px;left:0;right:0;bottom:0}@media (max-width: 767px){.container{top:62px}}main{position:relative;width:602px;max-width:100vw;padding:0;box-sizing:border-box;margin:30px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px}@media (max-width: 767px){main{margin:5px auto 15px}}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px}button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover{background:var(--button-bg-hover)}button:active{background:var(--button-bg-active)}button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}*:focus{outline:2px solid var(--focus-outline)}button::-moz-focus-inner{border:0}input:required,input:invalid{box-shadow:none} | ||||||
| body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-oaken.offline,body.theme-scarlet.offline,body.theme-seafoam.offline,body.theme-gecko.offline{--button-primary-bg:#ababab;--button-primary-text:#fff;--button-primary-border:#4d4d4d;--button-primary-bg-active:#9c9c9c;--button-primary-bg-hover:#b0b0b0;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#999;--main-bg:#fff;--body-bg:#fafafa;--body-text-color:#333;--main-border:#dadada;--svg-fill:#999;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#999;--nav-border:gray;--nav-a-border:#999;--nav-a-selected-border:#fff;--nav-a-selected-bg:#b3b3b3;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#bfbfbf;--nav-a-bg-hover:#a6a6a6;--nav-a-border-hover:#999;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#c7c7c7;--action-button-fill-color-hover:#d1d1d1;--action-button-fill-color-active:#a6a6a6;--action-button-fill-color-pressed:#878787;--action-button-fill-color-pressed-hover:#949494;--action-button-fill-color-pressed-active:#737373;--settings-list-item-bg:#fff;--settings-list-item-text:#999;--settings-list-item-text-hover:#999;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--mask-opaque-bg:rgba(51,51,51,0.8);--loading-bg:#ededed;--deemphasized-text-color:#666;--focus-outline:#bfbfbf;--very-deemphasized-link-color:rgba(153,153,153,0.6);--very-deemphasized-text-color:rgba(102,102,102,0.6);--status-direct-background:#ededed} | body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-oaken.offline,body.theme-scarlet.offline,body.theme-seafoam.offline,body.theme-gecko.offline{--button-primary-bg:#ababab;--button-primary-text:#fff;--button-primary-border:#4d4d4d;--button-primary-bg-active:#9c9c9c;--button-primary-bg-hover:#b0b0b0;--button-bg:#e6e6e6;--button-text:#333;--button-border:#a7a7a7;--button-bg-active:#bfbfbf;--button-bg-hover:#f2f2f2;--input-border:#dadada;--anchor-text:#999;--main-bg:#fff;--body-bg:#fafafa;--body-text-color:#333;--main-border:#dadada;--svg-fill:#999;--form-bg:#f7f7f7;--form-border:#c1c1c1;--nav-bg:#999;--nav-border:gray;--nav-a-border:#999;--nav-a-selected-border:#fff;--nav-a-selected-bg:#b3b3b3;--nav-svg-fill:#fff;--nav-text-color:#fff;--nav-a-selected-border-hover:#fff;--nav-a-selected-bg-hover:#bfbfbf;--nav-a-bg-hover:#a6a6a6;--nav-a-border-hover:#999;--nav-svg-fill-hover:#fff;--nav-text-color-hover:#fff;--action-button-fill-color:#c7c7c7;--action-button-fill-color-hover:#d1d1d1;--action-button-fill-color-active:#a6a6a6;--action-button-fill-color-pressed:#878787;--action-button-fill-color-pressed-hover:#949494;--action-button-fill-color-pressed-active:#737373;--settings-list-item-bg:#fff;--settings-list-item-text:#999;--settings-list-item-text-hover:#999;--settings-list-item-border:#dadada;--settings-list-item-bg-active:#e6e6e6;--settings-list-item-bg-hover:#fafafa;--toast-bg:#333;--toast-border:#fafafa;--toast-text:#fff;--mask-bg:#333;--mask-svg-fill:#fff;--mask-opaque-bg:rgba(51,51,51,0.8);--loading-bg:#ededed;--deemphasized-text-color:#666;--focus-outline:#bfbfbf;--very-deemphasized-link-color:rgba(153,153,153,0.6);--very-deemphasized-text-color:rgba(102,102,102,0.6);--status-direct-background:#ededed} | ||||||
| 
 | 
 | ||||||
| </style> | </style> | ||||||
|  | @ -83,6 +83,7 @@ body.offline,body.theme-hotpants.offline,body.theme-majesty.offline,body.theme-o | ||||||
| <symbol id="fa-user-times" viewBox="0 0 2048 1792"><title>Stop Following</title><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm1077 320l249 249q9 9 9 23 0 13-9 22l-136 136q-9 9-22 9-14 0-23-9l-249-249-249 249q-9 9-23 9-13 0-22-9l-136-136q-9-9-9-22 0-14 9-23l249-249-249-249q-9-9-9-23 0-13 9-22l136-136q9-9 22-9 14 0 23 9l249 249 249-249q9-9 23-9 13 0 22 9l136 136q9 9 9 22 0 14-9 23zm-498 0l-181 181q-37 37-37 91 0 53 37 90l83 83q-21 3-44 3H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 154 122 319 122t319-122q20-17 39-17 28 0 57 6-28 27-41 50t-13 56q0 54 37 91z"></path></symbol> | <symbol id="fa-user-times" viewBox="0 0 2048 1792"><title>Stop Following</title><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm1077 320l249 249q9 9 9 23 0 13-9 22l-136 136q-9 9-22 9-14 0-23-9l-249-249-249 249q-9 9-23 9-13 0-22-9l-136-136q-9-9-9-22 0-14 9-23l249-249-249-249q-9-9-9-23 0-13 9-22l136-136q9-9 22-9 14 0 23 9l249 249 249-249q9-9 23-9 13 0 22 9l136 136q9 9 9 22 0 14-9 23zm-498 0l-181 181q-37 37-37 91 0 53 37 90l83 83q-21 3-44 3H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 154 122 319 122t319-122q20-17 39-17 28 0 57 6-28 27-41 50t-13 56q0 54 37 91z"></path></symbol> | ||||||
| <symbol id="fa-user-plus" viewBox="0 0 2048 1792"><title>Follow</title><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm960 128h352q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-352v352q0 13-9.5 22.5t-22.5 9.5h-192q-13 0-22.5-9.5t-9.5-22.5v-352h-352q-13 0-22.5-9.5t-9.5-22.5v-192q0-13 9.5-22.5t22.5-9.5h352V672q0-13 9.5-22.5t22.5-9.5h192q13 0 22.5 9.5t9.5 22.5v352zm-736 224q0 52 38 90t90 38h256v238q-68 50-171 50H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 79 61 154.5 91.5T704 971t164.5-30.5T1023 849q20-17 39-17 132 0 217 96h-223q-52 0-90 38t-38 90v192z"></path></symbol> | <symbol id="fa-user-plus" viewBox="0 0 2048 1792"><title>Follow</title><path d="M704 896q-159 0-271.5-112.5T320 512t112.5-271.5T704 128t271.5 112.5T1088 512 975.5 783.5 704 896zm960 128h352q13 0 22.5 9.5t9.5 22.5v192q0 13-9.5 22.5t-22.5 9.5h-352v352q0 13-9.5 22.5t-22.5 9.5h-192q-13 0-22.5-9.5t-9.5-22.5v-352h-352q-13 0-22.5-9.5t-9.5-22.5v-192q0-13 9.5-22.5t22.5-9.5h352V672q0-13 9.5-22.5t22.5-9.5h192q13 0 22.5 9.5t9.5 22.5v352zm-736 224q0 52 38 90t90 38h256v238q-68 50-171 50H267q-121 0-194-69T0 1405q0-53 3.5-103.5t14-109T44 1084t43-97.5 62-81 85.5-53.5T346 832q19 0 39 17 79 61 154.5 91.5T704 971t164.5-30.5T1023 849q20-17 39-17 132 0 217 96h-223q-52 0-90 38t-38 90v192z"></path></symbol> | ||||||
| <symbol id="fa-external-link" viewBox="0 0 1792 1792"><title>External Link</title><path d="M1408 928v320q0 119-84.5 203.5T1120 1536H288q-119 0-203.5-84.5T0 1248V416q0-119 84.5-203.5T288 128h704q14 0 23 9t9 23v64q0 14-9 23t-23 9H288q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113V928q0-14 9-23t23-9h64q14 0 23 9t9 23zm384-864v512q0 26-19 45t-45 19-45-19l-176-176-652 652q-10 10-23 10t-23-10L695 983q-10-10-10-23t10-23l652-652-176-176q-19-19-19-45t19-45 45-19h512q26 0 45 19t19 45z"></path></symbol> | <symbol id="fa-external-link" viewBox="0 0 1792 1792"><title>External Link</title><path d="M1408 928v320q0 119-84.5 203.5T1120 1536H288q-119 0-203.5-84.5T0 1248V416q0-119 84.5-203.5T288 128h704q14 0 23 9t9 23v64q0 14-9 23t-23 9H288q-66 0-113 47t-47 113v832q0 66 47 113t113 47h832q66 0 113-47t47-113V928q0-14 9-23t23-9h64q14 0 23 9t9 23zm384-864v512q0 26-19 45t-45 19-45-19l-176-176-652 652q-10 10-23 10t-23-10L695 983q-10-10-10-23t10-23l652-652-176-176q-19-19-19-45t19-45 45-19h512q26 0 45 19t19 45z"></path></symbol> | ||||||
|  | <symbol id="fa-search" viewBox="0 0 1792 1792"><title>Search</title><path d="M1216 832q0-185-131.5-316.5T768 384 451.5 515.5 320 832t131.5 316.5T768 1280t316.5-131.5T1216 832zm512 832q0 52-38 90t-90 38q-54 0-90-38l-343-342q-179 124-399 124-143 0-273.5-55.5t-225-150-150-225T64 832t55.5-273.5 150-225 225-150T768 128t273.5 55.5 225 150 150 225T1472 832q0 220-124 399l343 343q37 37 37 90z"></path></symbol> | ||||||
| </svg><!-- end insert svg here --> | </svg><!-- end insert svg here --> | ||||||
|   </svg> |   </svg> | ||||||
| 	<!-- The application will be rendered inside this element, | 	<!-- The application will be rendered inside this element, | ||||||
|  |  | ||||||
		Ładowanie…
	
		Reference in New Issue
	
	 Nolan Lawson
						Nolan Lawson