Finish MVP of remoteStorage service pages/UI #202
							
								
								
									
										2
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						| @ -61,7 +61,7 @@ gem "sentry-rails" | ||||
| # Services | ||||
| gem 'discourse_api' | ||||
| gem "lnurl" | ||||
| gem 'manifique' | ||||
| gem 'manifique', '~> 1.1.0' | ||||
| gem 'nostr', '~> 0.6.0' | ||||
| 
 | ||||
| group :development, :test do | ||||
|  | ||||
| @ -245,7 +245,7 @@ GEM | ||||
|       net-imap | ||||
|       net-pop | ||||
|       net-smtp | ||||
|     manifique (1.0.1) | ||||
|     manifique (1.1.0) | ||||
|       faraday (~> 2.9.0) | ||||
|       faraday-follow_redirects (= 0.3.0) | ||||
|       nokogiri (~> 1.16.0) | ||||
| @ -515,7 +515,7 @@ DEPENDENCIES | ||||
|   listen (~> 3.2) | ||||
|   lnurl | ||||
|   lockbox | ||||
|   manifique | ||||
|   manifique (~> 1.1.0) | ||||
|   net-ldap | ||||
|   nostr (~> 0.6.0) | ||||
|   pagy (~> 6.0, >= 6.0.2) | ||||
|  | ||||
| @ -8,8 +8,7 @@ class Services::RemotestorageController < Services::BaseController | ||||
|     # unless current_user.service_enabled?(:remotestorage) | ||||
|     #   redirect_to service_remotestorage_info_path | ||||
|     # end | ||||
|     @rs_auths = current_user.remote_storage_authorizations | ||||
|     # TODO sort by app name | ||||
|     # @rs_apps_connected = current_user.remote_storage_authorizations.any? | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
|  | ||||
| @ -3,7 +3,12 @@ class Services::RsAuthsController < Services::BaseController | ||||
|   before_action :require_feature_enabled | ||||
|   before_action :require_service_available | ||||
|   # before_action :require_service_enabled | ||||
|   before_action :find_rs_auth | ||||
|   before_action :find_rs_auth, only: [:destroy, :launch_app] | ||||
| 
 | ||||
|   def index | ||||
|     @rs_auths = current_user.remote_storage_authorizations | ||||
|     # TODO sort by app name? | ||||
|   end | ||||
| 
 | ||||
|   def destroy | ||||
|     @auth.destroy! | ||||
|  | ||||
| @ -4,7 +4,7 @@ class WellKnownController < ApplicationController | ||||
|   def nostr | ||||
|     http_status :unprocessable_entity and return if params[:name].blank? | ||||
|     domain = request.headers["X-Forwarded-Host"].presence || Setting.primary_domain | ||||
|     relay_url = Setting.nostr_relay_url | ||||
|     relay_url = Setting.nostr_relay_url.presence | ||||
| 
 | ||||
|     if params[:name] == "_" | ||||
|       # pubkey for the primary domain without a username (e.g. kosmos.org) | ||||
|  | ||||
| @ -43,7 +43,7 @@ | ||||
|     </p> | ||||
|     <p> | ||||
|       We have run two 6-month trials so far, with the next trial period | ||||
|       starting sometime in Q2 2024. Watch your email for notifications about it! | ||||
|       starting sometime soon. Watch your email for notifications about it! | ||||
|     </p> | ||||
|   </section> | ||||
| <% end %> | ||||
|  | ||||
| @ -41,15 +41,16 @@ | ||||
|           <% end %> | ||||
|         </div> | ||||
|       <% end %> | ||||
|       <% if Setting.discourse_enabled? %> | ||||
|       <% if Setting.remotestorage_enabled? && | ||||
|             Flipper.enabled?(:remotestorage, current_user) %> | ||||
|         <div class="border border-gray-300 rounded-md hover:border-gray-400 | ||||
|                     bg-[length:80%] bg-center bg-no-repeat | ||||
|                     bg-[url(/img/logos/icon_discourse.svg)]"> | ||||
|           <%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/", | ||||
|                     bg-[length:80%] bg-[center_top_-156px] bg-no-repeat | ||||
|                     bg-[url(/img/logos/icon_remotestorage.svg)]"> | ||||
|           <%= link_to services_storage_path, | ||||
|                 class: "block h-full px-6 py-6 rounded-md" do %> | ||||
|             <h3 class="mb-3.5">Discourse</h3> | ||||
|             <h3 class="mb-3.5">Storage</h3> | ||||
|             <p class="text-gray-600"> | ||||
|               Community forums and support/help site | ||||
|               Sync your data between apps and devices | ||||
|             </p> | ||||
|           <% end %> | ||||
|         </div> | ||||
| @ -67,16 +68,15 @@ | ||||
|           <% end %> | ||||
|         </div> | ||||
|       <% end %> | ||||
|       <% if Setting.remotestorage_enabled? && | ||||
|             Flipper.enabled?(:remotestorage, current_user) %> | ||||
|       <% if Setting.discourse_enabled? %> | ||||
|         <div class="border border-gray-300 rounded-md hover:border-gray-400 | ||||
|                     bg-[length:80%] bg-[center_top_-156px] bg-no-repeat | ||||
|                     bg-[url(/img/logos/icon_remotestorage.svg)]"> | ||||
|           <%= link_to services_storage_path, | ||||
|                     bg-[length:80%] bg-center bg-no-repeat | ||||
|                     bg-[url(/img/logos/icon_discourse.svg)]"> | ||||
|           <%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/", | ||||
|                 class: "block h-full px-6 py-6 rounded-md" do %> | ||||
|             <h3 class="mb-3.5">Storage</h3> | ||||
|             <h3 class="mb-3.5">Discourse</h3> | ||||
|             <p class="text-gray-600"> | ||||
|               Sync your data between apps and devices | ||||
|               Community forums and support/help site | ||||
|             </p> | ||||
|           <% end %> | ||||
|         </div> | ||||
|  | ||||
| @ -2,15 +2,143 @@ | ||||
| 
 | ||||
| <%= render MainSimpleComponent.new do %> | ||||
|   <section> | ||||
|     <h3 class="mb-10">Connected Apps</h3> | ||||
|     <% if @rs_auths.any? %> | ||||
|     <div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12"> | ||||
|     <% @rs_auths.each do |auth| %> | ||||
|       <%= render RsAuthComponent.new(auth: auth) %> | ||||
|     <% end %> | ||||
|     <p class="mb-6"> | ||||
|       Store and synchronize your app data across different devices. | ||||
|     </p> | ||||
|   </section> | ||||
| 
 | ||||
|   <%= render partial: "shared/tabnav_remotestorage" %> | ||||
| 
 | ||||
|   <section> | ||||
|     <h3>Your Storage Address</h3> | ||||
|     <p class="mb-6"> | ||||
|       In order to connect an app to your storage account, give it your address: | ||||
|     </p> | ||||
|     <p data-controller="clipboard" class="flex gap-1 sm:w-2/5"> | ||||
|       <input type="text" id="user_address" class="grow" | ||||
|              value=<%= current_user.address %> disabled="disabled" | ||||
|              data-clipboard-target="source" /> | ||||
|       <button id="copy-user-address" class="btn-md btn-icon btn-outline shrink-0" | ||||
|               data-clipboard-target="trigger" data-action="clipboard#copy" | ||||
|               title="Copy to clipboard"> | ||||
|         <span class="content-initial"> | ||||
|           <%= render partial: "icons/copy", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %> | ||||
|         </span> | ||||
|         <span class="content-active hidden"> | ||||
|           <%= render partial: "icons/check", locals: { custom_class: "text-blue-600 h-4 w-4 inline" } %> | ||||
|         </span> | ||||
|       </button> | ||||
|     </p> | ||||
|   </section> | ||||
| 
 | ||||
|   <section> | ||||
|     <h3>Recommended Apps</h3> | ||||
|     <div data-controller="tabs" | ||||
|          data-tabs-active-tab-class="-mb-px border-gray-200 border-l border-t border-r rounded-t text-indigo-600 hover:text-indigo-600" | ||||
|          data-tabs-inactive-tab-class="text-gray-500 hover:text-gray-700" | ||||
|          class="mb-12"> | ||||
|       <select data-action="tabs#change" data-tabs-target="select" | ||||
|               class="block w-full mb-8 sm:hidden"> | ||||
|         <option>Productivity</option> | ||||
|         <option>Bookmarks</option> | ||||
|         <option>Reading</option> | ||||
|         <option>File sharing</option> | ||||
|         <option>Learning</option> | ||||
|       </select> | ||||
|       <ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> | ||||
|         <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> | ||||
|           <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline"> | ||||
|             Productivity | ||||
|           </a> | ||||
|         </li> | ||||
|         <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> | ||||
|           <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline"> | ||||
|             Bookmarks | ||||
|           </a> | ||||
|         </li> | ||||
|         <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> | ||||
|           <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline"> | ||||
|             Reading | ||||
|           </a> | ||||
|         </li> | ||||
|         <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> | ||||
|           <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline"> | ||||
|             File sharing | ||||
|           </a> | ||||
|         </li> | ||||
|         <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> | ||||
|           <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline"> | ||||
|             Learning | ||||
|           </a> | ||||
|         </li> | ||||
|       </ul> | ||||
| 
 | ||||
|       <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Hyperdraft", | ||||
|           description: "Create text notes and (optionally) turn them into a website", | ||||
|           icon_path: "/img/app_icons/hyperdraft.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://hyperdraft.rosano.ca"], | ||||
|           ] | ||||
|         ) %> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Notes Together", | ||||
|           description: "A powerful note-taking app, with support for attaching images and other files", | ||||
|           icon_path: "/img/app_icons/notes-together.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://notestogether.hominidsoftware.com"], | ||||
|           ] | ||||
|         ) %> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Papiers", | ||||
|           description: "A simple note-taking app", | ||||
|           icon_path: "/img/app_icons/papiers.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://papiers.gitlab.io"], | ||||
|           ] | ||||
|         ) %> | ||||
|       </div> | ||||
|       <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Webmarks", | ||||
|           description: "Archive your bookmarks in your remote storage", | ||||
|           icon_path: "/img/app_icons/webmarks.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://webmarks.5apps.com"], | ||||
|           ] | ||||
|         ) %> | ||||
|       </div> | ||||
|       <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Pétrolette", | ||||
|           description: "A news aggregator that syncs with your remote storage", | ||||
|           icon_path: "/img/app_icons/petrolette.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://petrolette.space"], | ||||
|           ] | ||||
|         ) %> | ||||
|       </div> | ||||
|       <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Sharesome", | ||||
|           description: "Quickly and easily share files from your remote storage", | ||||
|           icon_path: "/img/app_icons/sharesome.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://sharesome.5apps.com"], | ||||
|           ] | ||||
|         ) %> | ||||
|       </div> | ||||
|       <div class="hidden grid grid-cols-1 gap-6" data-tabs-target="panel"> | ||||
|         <%= render AppInfoComponent.new( | ||||
|           name: "Kommit", | ||||
|           description: "Create flashcards and learn them with spaced-repetition", | ||||
|           icon_path: "/img/app_icons/kommit.png", | ||||
|           links: [ | ||||
|             ["Web App", "https://kommit.rosano.ca"], | ||||
|           ] | ||||
|         ) %> | ||||
|       </div> | ||||
|     </div> | ||||
|     <% else %> | ||||
|     <p>No apps connected yet.</p> | ||||
|     <% end %> | ||||
|   </section> | ||||
| <% end %> | ||||
|  | ||||
							
								
								
									
										33
									
								
								app/views/services/rs_auths/index.html.erb
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,33 @@ | ||||
| <%= render HeaderComponent.new(title: "Storage") %> | ||||
| 
 | ||||
| <%= render MainSimpleComponent.new do %> | ||||
|   <section> | ||||
|     <p class="mb-6"> | ||||
|       Store and synchronize your app data across different devices. | ||||
|     </p> | ||||
|   </section> | ||||
| 
 | ||||
|   <%= render partial: "shared/tabnav_remotestorage" %> | ||||
| 
 | ||||
|   <section> | ||||
|     <% if @rs_auths.any? %> | ||||
|       <div class="w-full grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-y-10 gap-x-12 mt-4"> | ||||
|       <% @rs_auths.each do |auth| %> | ||||
|         <%= render RsAuthComponent.new(auth: auth) %> | ||||
|       <% end %> | ||||
|     </div> | ||||
|     <% else %> | ||||
|       <div class="text-center"> | ||||
|         <p class="mt-4 mb-12 inline-flex align-center items-center"> | ||||
|           <%= image_tag("/img/illustrations/undraw_friends_r511.svg", class: 'h-48') %> | ||||
|         </p> | ||||
|         <h3> | ||||
|           No apps connected | ||||
|         </h3> | ||||
|         <p class="text-gray-500"> | ||||
|           When connected, your apps will show up here. | ||||
|         </p> | ||||
|       </div> | ||||
|     <% end %> | ||||
|   </section> | ||||
| <% end %> | ||||
							
								
								
									
										14
									
								
								app/views/shared/_tabnav_remotestorage.html.erb
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,14 @@ | ||||
| <section> | ||||
|   <div class="border-b border-gray-200"> | ||||
|     <nav class="-mb-px flex" aria-label="Tabs"> | ||||
|       <%= render TabnavLinkComponent.new( | ||||
|         name: "Info", path: services_storage_path, | ||||
|         active: current_page?(services_storage_path) | ||||
|       ) %> | ||||
|       <%= render TabnavLinkComponent.new( | ||||
|         name: "Connected Apps", path: apps_services_storage_path, | ||||
|         active: current_page?(apps_services_storage_path) | ||||
|       ) %> | ||||
|     </nav> | ||||
|   </div> | ||||
| </section> | ||||
| @ -48,7 +48,8 @@ Rails.application.routes.draw do | ||||
|     end | ||||
| 
 | ||||
|     resource :storage, controller: 'remotestorage', only: [:show] do | ||||
|       resources :rs_auths, only: [:destroy] do | ||||
|       get :apps, to: "rs_auths#index" | ||||
|       resources :rs_auths, only: [:index, :destroy] do | ||||
|         member do | ||||
|           get :revoke, to: 'rs_auths#destroy' | ||||
|           get :launch_app | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/hyperdraft.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 31 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/kommit.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/notes-together.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/papiers.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/petrolette.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 2.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/sharesome.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 19 KiB | 
							
								
								
									
										
											BIN
										
									
								
								public/img/app_icons/webmarks.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										1
									
								
								public/img/illustrations/undraw_friends_r511.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 11 KiB | 
| @ -47,6 +47,10 @@ RSpec.describe "Well-known URLs", type: :request do | ||||
|       end | ||||
| 
 | ||||
|       context "without relay configured" do | ||||
|         before do | ||||
|           Setting.nostr_relay_url = "" | ||||
|         end | ||||
| 
 | ||||
|         it "does not include a recommended relay" do | ||||
|           get "/.well-known/nostr.json?name=bobdylan" | ||||
|           res = JSON.parse(response.body) | ||||
|  | ||||
| @ -4,6 +4,10 @@ RSpec.describe NostrManager::PublishZapReceipt, type: :model do | ||||
|   let(:user) { create :user, ln_account: "123456abcdef" } | ||||
|   let(:zap) { create :zap, user: user } | ||||
| 
 | ||||
|   before do | ||||
|     Setting.nostr_relay_url = "" | ||||
|   end | ||||
| 
 | ||||
|   describe "Default/delayed execution" do | ||||
|     it "publishes zap receipts to all requested relays" do | ||||
|       expect(NostrPublishEventJob).to receive(:perform_later) | ||||
|  | ||||