Compare commits

...

19 Commits

Author SHA1 Message Date
dfb5b6b794
Merge branch 'master' into live
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-12 15:39:15 +04:00
32b1c2748a
Fix wrong variable
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-30 20:22:04 +04:00
fc6cac8368
Remove superfluous link
All checks were successful
continuous-integration/drone/push Build is passing
Already linked in the same paragraph
2025-05-30 16:53:05 +04:00
eefdc88a47 Merge pull request 'Editable content' (#229) from feature/186-content_editing into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #229
Reviewed-by: Greg <greg@noreply.kosmos.org>
2025-05-30 11:14:50 +00:00
f2e8ca790c
Add Privacy and ToS pages, footer menu
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Release Drafter / Update release notes draft (pull_request) Successful in 3s
2025-05-30 13:27:15 +04:00
32cd4d896d
Fix link color for Devise links
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-29 17:26:18 +04:00
67c450860a
Fix tab links
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2025-05-29 16:24:33 +04:00
f1d9cf1e3d
Remove special link class
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
This cleans up the code quite a bit, but also allows links in editable
content to be rendered with the default style.
2025-05-29 16:10:34 +04:00
ab1490f472
Remove Kosmos name from wording
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
refs #222
2025-05-29 14:24:43 +04:00
6014134396
Finish MVP for content editing 2025-05-29 14:18:14 +04:00
6713665a61
WIP Rename "projects" page, make content editable
Some checks failed
continuous-integration/drone/push Build is failing
2025-05-28 18:42:10 +04:00
315cf4dd9f
Add editable content helpers 2025-05-28 18:41:53 +04:00
2f86b3c16f
Add admin/editable_contents controller 2025-05-28 18:40:54 +04:00
55c63be9e2
Memoize instance variable 2025-05-28 18:39:48 +04:00
5c8ffc2630
Add editable contents table 2025-05-28 18:39:25 +04:00
c7a21c7a69
Add top margin to h3 within content 2025-05-28 18:37:59 +04:00
252b0f1792
Revert "Add ActionText configs, update spec helpers/configs"
This reverts commit c9d23f829d7a7d57854eb311712db3c94dc7e31c.
2025-05-28 16:53:31 +04:00
57246ea76d
Fix navbar current link 2025-05-28 15:35:57 +04:00
c9d23f829d
Add ActionText configs, update spec helpers/configs 2025-05-28 14:52:31 +04:00
55 changed files with 352 additions and 168 deletions

View File

@ -42,6 +42,7 @@ gem 'flipper-active_record'
gem 'flipper-ui' gem 'flipper-ui'
gem 'gpgme', '~> 2.0.24' gem 'gpgme', '~> 2.0.24'
gem 'zbase32', '~> 0.1.1' gem 'zbase32', '~> 0.1.1'
gem 'kramdown'
# HTTP requests # HTTP requests
gem 'faraday' gem 'faraday'

View File

@ -549,6 +549,7 @@ DEPENDENCIES
image_processing (~> 1.12.2) image_processing (~> 1.12.2)
importmap-rails importmap-rails
jbuilder (~> 2.7) jbuilder (~> 2.7)
kramdown
letter_opener letter_opener
letter_opener_web letter_opener_web
listen (~> 3.2) listen (~> 3.2)

View File

@ -7,7 +7,6 @@
@import "components/buttons"; @import "components/buttons";
@import "components/dashboard_services"; @import "components/dashboard_services";
@import "components/forms"; @import "components/forms";
@import "components/links";
@import "components/notifications"; @import "components/notifications";
@import "components/pagination"; @import "components/pagination";
@import "components/tables"; @import "components/tables";

View File

@ -6,6 +6,7 @@
body { body {
@apply leading-none bg-cover bg-fixed; @apply leading-none bg-cover bg-fixed;
background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg'); background-image: linear-gradient(35deg, rgba(255,0,255,0.2) 0, rgba(13,79,153,0.8) 100%), url('/img/bg-1.jpg');
color: black;
} }
body#admin { body#admin {
@ -32,6 +33,10 @@
@apply pt-8 sm:pt-12; @apply pt-8 sm:pt-12;
} }
main section h3:not(:first-child) {
@apply mt-8;
}
main section:first-of-type { main section:first-of-type {
@apply pt-0; @apply pt-0;
} }
@ -55,4 +60,11 @@
main ul li { main ul li {
@apply leading-6; @apply leading-6;
} }
main a:not(nav > *) {
@apply text-blue-600;
&:hover { @apply underline; }
&:visited { @apply text-indigo-600; }
&:active { @apply text-red-600; }
}
} }

View File

@ -1,5 +1,15 @@
@layer components { @layer components {
.btn-text-dark { @apply text-black; }
.btn-text-dark:hover { @apply text-black no-underline; }
.btn-text-dark:visited { @apply text-black; }
.btn-text-dark:active { @apply text-black; }
.btn-text-light { @apply text-white; }
.btn-text-light:hover { @apply text-white no-underline; }
.btn-text-light:visited { @apply text-white; }
.btn-text-light:active { @apply text-white; }
.btn { .btn {
@apply btn-text-dark;
@apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center @apply inline-block font-semibold rounded-md leading-none cursor-pointer text-center
transition-colors duration-75 focus:outline-none focus:ring-4; transition-colors duration-75 focus:outline-none focus:ring-4;
} }
@ -28,17 +38,20 @@
} }
.btn-blue { .btn-blue {
@apply bg-blue-500 hover:bg-blue-600 text-white @apply btn-text-light;
@apply bg-blue-500 hover:bg-blue-600
focus:ring-blue-400 focus:ring-opacity-75; focus:ring-blue-400 focus:ring-opacity-75;
} }
.btn-emerald { .btn-emerald {
@apply bg-emerald-500 hover:bg-emerald-600 text-white @apply btn-text-light;
@apply bg-emerald-500 hover:bg-emerald-600
focus:ring-emerald-400 focus:ring-opacity-75; focus:ring-emerald-400 focus:ring-opacity-75;
} }
.btn-red { .btn-red {
@apply bg-red-600 hover:bg-red-700 text-white @apply btn-text-light;
@apply bg-red-600 hover:bg-red-700
focus:ring-red-500 focus:ring-opacity-75; focus:ring-red-500 focus:ring-opacity-75;
} }

View File

@ -1,8 +0,0 @@
@layer components {
.ks-text-link {
@apply text-blue-600;
&:hover { @apply underline; }
&:visited { @apply text-indigo-600; }
&:active { @apply text-red-600; }
}
}

View File

@ -34,7 +34,7 @@
.pagy-nav .page a, .page.gap { .pagy-nav .page a, .page.gap {
@apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative @apply bg-white border-gray-300 text-gray-500 hover:bg-gray-100 relative
inline-flex items-center border px-4 py-2 text-sm font-medium inline-flex items-center border px-4 py-2 text-sm font-medium
focus:z-20; no-underline focus:z-20;
} }
.pagy-nav .page.active { .pagy-nav .page.active {

View File

@ -0,0 +1,30 @@
<div class="inline-block text-left" data-controller="modal" data-action="keydown.esc->modal#close">
<button class="btn-md btn-outline text-red-600" data-action="click->modal#open" title="Edit">
<%= content || "Edit" %>
</button>
<%= render ModalComponent.new(show_close_button: false) do %>
<%= form_with model: [:admin, @editable_content],
html: { autocomplete: "off" } do |form| %>
<%= form.hidden_field :redirect_to, value: @redirect_to %>
<p class="mb-2">
<%= form.label :content, @editable_content.key.capitalize, class: 'font-bold' %>
</p>
<% if @editable_content.rich_text %>
<p>
<%= form.textarea :content, class: "md:w-[56rem] md:h-[28rem]" %>
</p>
<p class="text-right">
<%= form.submit "Save", class: "ml-2 btn-md btn-blue" %>
</p>
<% else %>
<p class="">
<%= form.text_field :content, class: "w-80" %>
</p>
<p>
<%= form.submit "Save", class: "btn-md btn-blue w-full" %>
</p>
<% end %>
<% end %>
<% end %>
</div>

View File

@ -0,0 +1,6 @@
class EditContentButtonComponent < ViewComponent::Base
def initialize(context:, key:, rich_text: false, redirect_to: nil)
@editable_content = EditableContent.find_or_create_by(context:, key:, rich_text:)
@redirect_to = redirect_to
end
end

View File

@ -0,0 +1,9 @@
<% if @editable_content.has_content? %>
<% if @editable_content.rich_text %>
<%= helpers.markdown_to_html @editable_content.content %>
<% else %>
<%= @editable_content.content %>
<% end %>
<% else %>
<%= @default %>
<% end %>

View File

@ -0,0 +1,6 @@
class EditableContentComponent < ViewComponent::Base
def initialize(context:, key:, rich_text: false, default: nil)
@editable_content = EditableContent.find_or_create_by(context:, key:, rich_text:)
@default = default
end
end

View File

@ -1,4 +1,4 @@
<main class="w-full max-w-xl mx-auto pb-12 px-4 sm:px-6 lg:px-8"> <main class="w-full max-w-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12"> <div class="bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %> <%= content %>
</div> </div>

View File

@ -1,4 +1,4 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
<div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12"> <div class="md:min-h-[50vh] bg-white rounded-lg shadow px-6 sm:px-12 py-8 sm:py-12">
<%= content %> <%= content %>
</div> </div>

View File

@ -1,4 +1,4 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
<div class="bg-white rounded-lg shadow"> <div class="bg-white rounded-lg shadow">
<div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x"> <div class="md:min-h-[50vh] divide-y divide-gray-200 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
<aside class="py-6 sm:py-8 lg:col-span-3"> <aside class="py-6 sm:py-8 lg:col-span-3">

View File

@ -1,4 +1,4 @@
<main class="w-full max-w-6xl mx-auto pb-12 px-4 md:px-6 lg:px-8"> <main class="w-full max-w-6xl mx-auto px-4 md:px-6 lg:px-8">
<div class="md:min-h-[50vh] bg-white rounded-lg shadow"> <div class="md:min-h-[50vh] bg-white rounded-lg shadow">
<div class="px-6 sm:px-12 pt-2 sm:pt-4"> <div class="px-6 sm:px-12 pt-2 sm:pt-4">
<%= render partial: @tabnav_partial %> <%= render partial: @tabnav_partial %>

View File

@ -0,0 +1,45 @@
class Admin::EditableContentsController < Admin::BaseController
before_action :set_content, only: [:show, :edit, :update]
before_action :set_current_section, only: [:index, :show, :edit]
def index
@path = params[:path].presence
scope = EditableContent.order(path: :asc)
scope = scope.where(path: @path) if @path
@pagy, @contents = pagy(scope)
end
def show
end
def edit
end
def update
return_to = params[:editable_content][:redirect_to].presence
if @editable_content.update(content_params)
if return_to
redirect_to return_to
else
render status: :ok
end
else
render :edit, status: :unprocessable_entity
end
end
private
def set_content
@editable_content = EditableContent.find(params[:id])
end
def content_params
params.require(:editable_content).permit(:path, :key, :lang, :content, :rich_text)
end
def set_current_section
@current_section = :content
end
end

View File

@ -0,0 +1,16 @@
class Contributions::OtherController < ApplicationController
before_action :authenticate_user!
before_action :set_content_editing
# GET /contributions/other
def index
@current_section = :contributions
end
private
def set_content_editing
return unless params[:edit] && current_user.is_admin?
@edit_content = true
end
end

View File

@ -1,8 +0,0 @@
class Contributions::ProjectsController < ApplicationController
before_action :authenticate_user!
# GET /contributions
def index
@current_section = :contributions
end
end

View File

@ -0,0 +1,9 @@
class PagesController < ApplicationController
def privacy
@current_section = :privacy
end
def tos
@current_section = :tos
end
end

View File

@ -15,6 +15,10 @@ module ApplicationHelper
tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800" tag.span text, class: "inline-flex items-center rounded-full bg-#{color}-100 px-2.5 py-0.5 text-xs font-medium text-#{color}-800"
end end
def markdown_to_html(string)
raw Kramdown::Document.new(string, { input: "GFM" }).to_html
end
def image_url_for(attachment) def image_url_for(attachment)
return s3_image_url(attachment) if Setting.s3_enabled? return s3_image_url(attachment) if Setting.s3_enabled?

View File

@ -0,0 +1,12 @@
class EditableContent < ApplicationRecord
validates :key, presence: true,
uniqueness: { scope: :context }
def has_content?
content.present?
end
def is_empty?
content.blank?
end
end

View File

@ -131,11 +131,11 @@ class User < ApplicationRecord
end end
def is_admin? def is_admin?
admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin) @admin ||= if admin = Devise::LDAP::Adapter.get_ldap_param(self.cn, :admin)
!!admin.first !!admin.first
else else
false false
end end
end end
def address def address

View File

@ -11,7 +11,7 @@ module EjabberdManager
def call def call
unless @overwrite unless @overwrite
current_avatar = EjabberdManager::GetAvatar.call(user: @user) current_avatar = EjabberdManager::GetAvatar.call(user: @user)
Rails.logger.info { "User #{user.cn} already has an avatar set" } Rails.logger.info { "User #{@user.cn} already has an avatar set" }
return if current_avatar.present? return if current_avatar.present?
end end

View File

@ -38,8 +38,7 @@
<tr> <tr>
<td><%= web_app.name %></td> <td><%= web_app.name %></td>
<td><%= link_to web_app.url, web_app.url, <td><%= link_to web_app.url, web_app.url,
target: "_blank", rel: "nofollow noopener", target: "_blank", rel: "nofollow noopener" %></td>
class: "ks-text-link" %></td>
<td class="hidden md:table-cell"><%= web_app.remote_storage_authorizations.count %></td> <td class="hidden md:table-cell"><%= web_app.remote_storage_authorizations.count %></td>
<td class="hidden md:table-cell"> <td class="hidden md:table-cell">
<span title="<%= web_app.created_at %>" class="cursor-help"> <span title="<%= web_app.created_at %>" class="cursor-help">

View File

@ -12,7 +12,7 @@
<tbody> <tbody>
<% donations.each do |donation| %> <% donations.each do |donation| %>
<tr> <tr>
<td><%= link_to donation.user.cn, admin_user_path(donation.user.cn), class: 'ks-text-link' %></td> <td><%= link_to donation.user.cn, admin_user_path(donation.user.cn) %></td>
<td class="text-right"><% if donation.amount_sats.present? %><%= number_with_delimiter donation.amount_sats %><% end %></td> <td class="text-right"><% if donation.amount_sats.present? %><%= number_with_delimiter donation.amount_sats %><% end %></td>
<td class="text-right"><% if donation.fiat_amount.present? %><%= number_to_currency donation.fiat_amount.to_f / 100, unit: "" %> <%= donation.fiat_currency %><% end %></td> <td class="text-right"><% if donation.fiat_amount.present? %><%= number_to_currency donation.fiat_amount.to_f / 100, unit: "" %> <%= donation.fiat_currency %><% end %></td>
<td class="pl-2"><%= donation.public_name %></td> <td class="pl-2"><%= donation.public_name %></td>

View File

@ -6,7 +6,7 @@
<tbody> <tbody>
<tr> <tr>
<th>User</th> <th>User</th>
<td><%= link_to @donation.user.cn, admin_user_path(@donation.user.cn), class: 'ks-text-link' %></td> <td><%= link_to @donation.user.cn, admin_user_path(@donation.user.cn) %></td>
</tr> </tr>
<tr> <tr>
<th>Donation Method</th> <th>Donation Method</th>

View File

@ -44,8 +44,8 @@
<tr> <tr>
<td class="overflow-ellipsis font-mono"><%= invitation.token %></td> <td class="overflow-ellipsis font-mono"><%= invitation.token %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td> <td><%= invitation.used_at.strftime("%Y-%m-%d (%H:%M UTC)") %></td>
<td><%= link_to invitation.user.cn, admin_user_path(invitation.user.cn), class: "ks-text-link" %></td> <td><%= link_to invitation.user.cn, admin_user_path(invitation.user.cn) %></td>
<td><%= link_to invitation.invitee.cn, admin_user_path(invitation.invitee.cn), class: "ks-text-link" %></td> <td><%= link_to invitation.invitee.cn, admin_user_path(invitation.invitee.cn) %></td>
</tr> </tr>
<% end %> <% end %>
</tbody> </tbody>

View File

@ -36,7 +36,7 @@
</td> </td>
<td> <td>
<% if user = @users.find{ |u| u[2] == account.login } %> <% if user = @users.find{ |u| u[2] == account.login } %>
<%= link_to user[0], admin_user_path(user[0]), class: "ks-text-link" %> <%= link_to user[0], admin_user_path(user[0]) %>
<% end %> <% end %>
</td> </td>
<td><%= number_with_delimiter account.balance.to_i.to_s %></td> <td><%= number_with_delimiter account.balance.to_i.to_s %></td>

View File

@ -42,7 +42,7 @@
<tbody> <tbody>
<% @users.each do |user| %> <% @users.each do |user| %>
<tr> <tr>
<td><%= link_to(user.cn, admin_user_path(user.cn), class: 'ks-text-link') %></td> <td><%= link_to(user.cn, admin_user_path(user.cn)) %></td>
<td> <td>
<%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %> <%= user.confirmed_at.nil? ? badge("pending", :yellow) : "" %>
<% if @show_contributors %><%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %><% end %> <% if @show_contributors %><%= @contributors.include?(user.cn) ? badge("contributor", :green) : "" %><% end %>

View File

@ -47,7 +47,7 @@
<th>Donations</th> <th>Donations</th>
<td> <td>
<% if @user.donations.any? %> <% if @user.donations.any? %>
<%= link_to admin_donations_path(username: @user.cn), class: "ks-text-link" do %> <%= link_to admin_donations_path(username: @user.cn) do %>
<%= @user.donations.completed.count %> for <%= @user.donations.completed.count %> for
<%= number_with_delimiter @user.donations.completed.sum("amount_sats") %> sats <%= number_with_delimiter @user.donations.completed.sum("amount_sats") %> sats
<% end %> <% end %>
@ -60,7 +60,7 @@
<th>Invited by</th> <th>Invited by</th>
<td> <td>
<% if @user.inviter %> <% if @user.inviter %>
<%= link_to @user.inviter.cn, admin_user_path(@user.inviter.cn), class: 'ks-text-link' %> <%= link_to @user.inviter.cn, admin_user_path(@user.inviter.cn) %>
<% else %>&mdash;<% end %> <% else %>&mdash;<% end %>
</td> </td>
</tr> </tr>
@ -102,10 +102,10 @@
<% if @invitees.any? %> <% if @invitees.any? %>
<ul class="mb-0"> <ul class="mb-0">
<% @recent_invitees.each do |invitee| %> <% @recent_invitees.each do |invitee| %>
<li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn), class: "ks-text-link" %></li> <li class="leading-none mb-2 last:mb-0"><%= link_to invitee.cn, admin_user_path(invitee.cn) %></li>
<% end %> <% end %>
<% if @more_invitees > 0 %> <% if @more_invitees > 0 %>
<li>and <%= link_to "#{@more_invitees} more", admin_invitations_path(username: @user.cn), class: "ks-text-link" %></li> <li>and <%= link_to "#{@more_invitees} more", admin_invitations_path(username: @user.cn) %></li>
<% end %> <% end %>
</ul> </ul>
<% else %>&mdash;<% end %> <% else %>&mdash;<% end %>
@ -168,7 +168,7 @@
<span class="font-mono" title="<%= @user.pgp_fpr %>"> <span class="font-mono" title="<%= @user.pgp_fpr %>">
<% if @user.pgp_pubkey_contains_user_address? %> <% if @user.pgp_pubkey_contains_user_address? %>
<%= link_to wkd_key_url(hashed_username: @user.wkd_hash, l: @user.cn, format: :txt), <%= link_to wkd_key_url(hashed_username: @user.wkd_hash, l: @user.cn, format: :txt),
class: "ks-text-link", target: "_blank" do %> target: "_blank" do %>
<%= "#{@user.pgp_fpr[0, 8]}…#{@user.pgp_fpr[-8..-1]}" %> <%= "#{@user.pgp_fpr[0, 8]}…#{@user.pgp_fpr[-8..-1]}" %>
<% end %> <% end %>
<% else %> <% else %>

View File

@ -3,7 +3,7 @@
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %> <%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section> <section>
<p class="mb-12"> <p class="mb-12">
Your financial contributions to the development and upkeep of Kosmos Your financial contributions to the development and upkeep of our
software and services. software and services.
</p> </p>
</section> </section>

View File

@ -0,0 +1,20 @@
<%= render HeaderComponent.new(title: "Contributions") %>
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<%= render EditableContentComponent.new(
context: "contributions/other", key: "body", rich_text: true,
default: "No content yet") %>
<% if current_user.is_admin? %>
<div class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= render EditContentButtonComponent.new(
context: "contributions/other", key: "title",
redirect_to: request.path) do %>Edit title<% end %>
<%= render EditContentButtonComponent.new(
context: "contributions/other", key: "body", rich_text: true,
redirect_to: request.path) do %>Edit content<% end %>
</div>
<% end %>
</section>
<% end %>

View File

@ -1,49 +0,0 @@
<%= render HeaderComponent.new(title: "Contributions") %>
<%= render MainWithTabnavComponent.new(tabnav_partial: "shared/tabnav_contributions") do %>
<section>
<p class="mb-8">
Project contributions are how we develop and run all Kosmos software and
services. Everything we create and provide is free and open-source
software, even the page you're looking at right now!
</p>
<h3>Start contributing</h3>
<p>
Check out our
<a href="https://kosmos.org/projects/" target="_blank" class="ks-text-link">projects page</a>
for some (but not all) potential places that can use your help.
</p>
<p>
There's something to do for everyone, especially non-programmers! For
example, we need more help with graphics, UI/UX design, and
content/copywriting. Also, testing any of our software and reporting
issues you encounter along the way is very valuable.
</p>
<p>
A good way to get started is to join one of our
<a href="https://wiki.kosmos.org/Main_Page#Chat" target="_blank" class="ks-text-link">chat rooms</a>
and introduce yourself. Alternatively, you can also ping us on any other
medium, or even just grab an open issue on our
<a href="https://gitea.kosmos.org/kosmos/" target="_blank" class="ks-text-link">Gitea</a>
or on
<a href="https://github.com/67P/" target="_blank" class="ks-text-link">GitHub</a>
and dive right in.
</p>
<p class="mb-8">
Last but not least, if you want to help by proposing new features or
services, or by giving feedback on existing ones, head over to the
<a href="https://community.kosmos.org/" target="_blank" class="ks-text-link">community forums</a>,
where you can do just that.
</p>
<h3>Open Source Grants</h3>
<p>
Money coming in from financial contributions is first used to pay for our
bills. Additional funds are being paid out directly to our contributors,
including you, according to their rough share of contributions.
</p>
<p>
We have run two 6-month trials so far, with the next trial period
starting sometime soon. Watch your email for notifications about it!
</p>
</section>
<% end %>

View File

@ -8,7 +8,7 @@
bg-[length:86%] bg-[center_top_-40px] bg-no-repeat bg-[length:86%] bg-[center_top_-40px] bg-no-repeat
bg-[url(/img/logos/icon_xmpp.svg)]"> bg-[url(/img/logos/icon_xmpp.svg)]">
<%= link_to services_chat_path, <%= link_to services_chat_path,
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Chat</h3> <h3 class="mb-3.5">Chat</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Federated chat rooms and instant messaging Federated chat rooms and instant messaging
@ -20,7 +20,8 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400 <div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:88%] bg-[center_top_-40px] bg-no-repeat bg-[length:88%] bg-[center_top_-40px] bg-no-repeat
bg-[url(/img/logos/icon_mastodon.svg)]"> bg-[url(/img/logos/icon_mastodon.svg)]">
<%= link_to services_mastodon_path, class: "block h-full px-6 py-6 rounded-md" do %> <%= link_to services_mastodon_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Mastodon</h3> <h3 class="mb-3.5">Mastodon</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Your account on the Open Social Web Your account on the Open Social Web
@ -33,7 +34,8 @@
<div class="border border-gray-300 rounded-md hover:border-gray-400 <div class="border border-gray-300 rounded-md hover:border-gray-400
bg-[length:90%] bg-[center_top_-160px] bg-no-repeat bg-[length:90%] bg-[center_top_-160px] bg-no-repeat
bg-[url(/img/logos/icon_mail.svg)]"> bg-[url(/img/logos/icon_mail.svg)]">
<%= link_to services_email_path, class: "block h-full px-6 py-6 rounded-md" do %> <%= link_to services_email_path,
class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">E-Mail</h3> <h3 class="mb-3.5">E-Mail</h3>
<p class="text-gray-600"> <p class="text-gray-600">
A no-bullshit email account A no-bullshit email account
@ -47,7 +49,7 @@
bg-[length:80%] bg-[center_top_-156px] bg-no-repeat bg-[length:80%] bg-[center_top_-156px] bg-no-repeat
bg-[url(/img/logos/icon_remotestorage.svg)]"> bg-[url(/img/logos/icon_remotestorage.svg)]">
<%= link_to services_storage_path, <%= link_to services_storage_path,
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Storage</h3> <h3 class="mb-3.5">Storage</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Sync your data between apps and devices Sync your data between apps and devices
@ -60,7 +62,7 @@
bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat bg-cover bg-center sm:bg-[center_top_-140px] bg-no-repeat
bg-[url(/img/logos/icon_lightning.svg)]"> bg-[url(/img/logos/icon_lightning.svg)]">
<%= link_to services_lightning_index_path, <%= link_to services_lightning_index_path,
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Lightning Network</h3> <h3 class="mb-3.5">Lightning Network</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Send and receive sats over the Bitcoin Lightning Network Send and receive sats over the Bitcoin Lightning Network
@ -73,7 +75,7 @@
bg-[length:80%] bg-center bg-no-repeat bg-[length:80%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_discourse.svg)]"> bg-[url(/img/logos/icon_discourse.svg)]">
<%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/", <%= link_to "#{Setting.discourse_public_url}/session/sso?return_path=/",
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Discourse</h3> <h3 class="mb-3.5">Discourse</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Community forums and support/help site Community forums and support/help site
@ -86,7 +88,7 @@
bg-[length:92%] bg-center bg-no-repeat bg-[length:92%] bg-center bg-no-repeat
bg-[url(/img/logos/icon_gitea.png)]"> bg-[url(/img/logos/icon_gitea.png)]">
<%= link_to Setting.gitea_public_url, <%= link_to Setting.gitea_public_url,
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Gitea</h3> <h3 class="mb-3.5">Gitea</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Code hosting and collaboration for software projects Code hosting and collaboration for software projects
@ -99,7 +101,7 @@
bg-[length:86%] bg-[center_top_-60px] bg-no-repeat bg-[length:86%] bg-[center_top_-60px] bg-no-repeat
bg-[url(/img/logos/icon_droneci.svg)]"> bg-[url(/img/logos/icon_droneci.svg)]">
<%= link_to Setting.droneci_public_url, <%= link_to Setting.droneci_public_url,
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Drone CI</h3> <h3 class="mb-3.5">Drone CI</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Continuous integration for software projects on Gitea Continuous integration for software projects on Gitea
@ -112,10 +114,10 @@
bg-cover bg-[center_top_-20px] bg-no-repeat bg-cover bg-[center_top_-20px] bg-no-repeat
bg-[url(/img/logos/icon_mediawiki.svg)]"> bg-[url(/img/logos/icon_mediawiki.svg)]">
<%= link_to Setting.mediawiki_public_url, <%= link_to Setting.mediawiki_public_url,
class: "block h-full px-6 py-6 rounded-md" do %> class: "block h-full px-6 py-6 rounded-md btn-text-dark" do %>
<h3 class="mb-3.5">Wiki</h3> <h3 class="mb-3.5">Wiki</h3>
<p class="text-gray-600"> <p class="text-gray-600">
Kosmos documentation and knowledge base Documentation and knowledge base
</p> </p>
<% end %> <% end %>
</div> </div>

View File

@ -47,7 +47,7 @@
data: { action: "click->settings--toggle#toggleSwitch" } %> data: { action: "click->settings--toggle#toggleSwitch" } %>
<p class="grow text-sm text-right"> <p class="grow text-sm text-right">
<%= link_to "Forgot your password?", new_password_path(resource_name), <%= link_to "Forgot your password?", new_password_path(resource_name),
class: "text-gray-500 underline" %><br /> class: "text-gray-500 visited:text-gray-500 underline" %><br />
</p> </p>
<% end %> <% end %>

View File

@ -2,28 +2,28 @@
<%- if controller_name != 'sessions' %> <%- if controller_name != 'sessions' %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Log in", new_session_path(resource_name), <%= link_to "Log in", new_session_path(resource_name),
class: "text-gray-500 underline" %> class: "text-gray-500 visited:text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Forgot your password?", new_password_path(resource_name), <%= link_to "Forgot your password?", new_password_path(resource_name),
class: "text-gray-500 underline" %> class: "text-gray-500 visited:text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %> <%- if devise_mapping.confirmable? && !controller_name.match(/^(confirmations|sessions)$/) %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name),
class: "text-gray-500 underline" %> class: "text-gray-500 visited:text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<p class="mb-2"> <p class="mb-2">
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name),
class: "text-gray-500 underline" %> class: "text-gray-500 visited:text-gray-500 underline" %>
</p> </p>
<% end %> <% end %>
</div> </div>

View File

@ -4,7 +4,7 @@
<section> <section>
<% if @invitations_unused.any? %> <% if @invitations_unused.any? %>
<p class="mb-8"> <p class="mb-8">
Invite your friends to a Kosmos account by sharing an invitation URL with them: Invite your friends to register an account by sharing an invitation URL with them:
</p> </p>
<ul class="md:w-3/4"> <ul class="md:w-3/4">
<% @invitations_unused.each do |invitation| %> <% @invitations_unused.each do |invitation| %>

View File

@ -10,7 +10,7 @@
<%= javascript_importmap_tags %> <%= javascript_importmap_tags %>
</head> </head>
<body <%= @context.present? ? "id=#{@context}" : "" %> class="h-full <%= @context == :admin ? "bg-red-500" : "bg-sky-900" %>"> <body <%= @context.present? ? "id=#{@context}" : "" %> class="h-full <%= @context == :admin ? "bg-red-500" : "bg-sky-900" %>">
<div class="min-h-full"> <div class="min-h-full flex flex-col">
<nav data-controller="topbar"> <nav data-controller="topbar">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8"> <div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="border-b border-gray-200/10"> <div class="border-b border-gray-200/10">
@ -96,7 +96,18 @@
</div> </div>
</div> </div>
<%= yield %> <div class="flex-grow">
<%= yield %>
</div>
<% if user_signed_in? && current_user.confirmed? %>
<nav id="footer" class="max-w-6xl mx-auto pt-4 sm:px-6 lg:px-8">
<div class="flex justify-end items-center space-x-4 h-16">
<%= link_to "Terms", tos_page_path, class: main_nav_class(@current_section, :tos) %>
<%= link_to "Privacy", privacy_page_path, class: main_nav_class(@current_section, :privacy) %>
</div>
</nav>
<% end %>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
Hi <%= @user.display_name.presence || @user.cn %>, Hi <%= @user.display_name.presence || @user.cn %>,
New invitations have just been added to your Kosmos account, so you can invite more people to our cooperative services: New invitations have just been added to your account, so you can invite more people to our cooperative services:
<%= invitations_url %> <%= invitations_url %>

View File

@ -1,6 +1,6 @@
Hi <%= @user.display_name.presence || @user.cn %>, Hi <%= @user.display_name.presence || @user.cn %>,
You have just granted '<%= @auth.client_id %>' access to your Kosmos Storage, with the following permissions: You have just granted '<%= @auth.client_id %>' access to your remote storage, with the following permissions:
<% @permissions.each do |p| %> <% @permissions.each do |p| %>
* <%= p %> * <%= p %>

View File

@ -0,0 +1,17 @@
<%= render HeaderComponent.new(title: "Privacy Policy") %>
<%= render MainSimpleComponent.new do %>
<section>
<%= render EditableContentComponent.new(
context: "privacy", key: "body", rich_text: true,
default: "No content yet. Please add a privacy policy.") %>
<% if current_user.is_admin? %>
<div class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= render EditContentButtonComponent.new(
context: "privacy", key: "body", rich_text: true,
redirect_to: request.path) do %>Edit content<% end %>
</div>
<% end %>
</section>
<% end %>

View File

@ -0,0 +1,17 @@
<%= render HeaderComponent.new(title: "Terms of Service") %>
<%= render MainSimpleComponent.new do %>
<section>
<%= render EditableContentComponent.new(
context: "tos", key: "body", rich_text: true,
default: "No content yet. Please add your terms of service.") %>
<% if current_user.is_admin? %>
<div class="mt-8 pt-6 border-t border-gray-200 text-right">
<%= render EditContentButtonComponent.new(
context: "tos", key: "body", rich_text: true,
redirect_to: request.path) do %>Edit content<% end %>
</div>
<% end %>
</section>
<% end %>

View File

@ -4,7 +4,7 @@
<section class="permissions"> <section class="permissions">
<p class="mb-8"> <p class="mb-8">
The app on The app on
<%= link_to @client_id, "https://#{@client_id}", class: "ks-text-link" %> <%= link_to @client_id, "https://#{@client_id}" %>
is asking for access to these folders: is asking for access to these folders:
</p> </p>

View File

@ -62,35 +62,30 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <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"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Android Android
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
iOS iOS
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Linux Linux
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Windows Windows
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
macOS macOS
</a> </a>
</li> </li>
<!-- <li class="mr&#45;2" data&#45;tabs&#45;target="tab" data&#45;action="click&#45;>tabs#change"> -->
<!-- <a href="#" class="bg&#45;white inline&#45;block py&#45;2 px&#45;4 font&#45;semibold no&#45;underline"> -->
<!-- Web -->
<!-- </a> -->
<!-- </li> -->
</ul> </ul>
<div id="apps-android" class="hidden grid grid-cols-1 gap-6" <div id="apps-android" class="hidden grid grid-cols-1 gap-6"

View File

@ -34,7 +34,7 @@
<p> <p>
Your email password is different from your main account password. You can Your email password is different from your main account password. You can
reset your email password in the reset your email password in the
<%= link_to "email settings", setting_path(:email), class: "ks-text-link" %>. <%= link_to "email settings", setting_path(:email) %>.
</p> </p>
</section> </section>
<section> <section>
@ -56,22 +56,22 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <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"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Android Android
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Linux Linux
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Windows Windows
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
macOS macOS
</a> </a>
</li> </li>

View File

@ -8,8 +8,8 @@
<section> <section>
<h3>Lightning Address</h3> <h3>Lightning Address</h3>
<p class="mb-6"> <p class="mb-6">
Your Kosmos user address is also a Your user address is also a
<a class="ks-text-link" href="https://lightningaddress.com/" target="_blank">Lightning Address</a>! <a href="https://lightningaddress.com/" target="_blank">Lightning Address</a>!
The easiest way to receive sats is by just giving out your address: The easiest way to receive sats is by just giving out your address:
</p> </p>
<p data-controller="clipboard" class="flex gap-1 sm:w-2/5"> <p data-controller="clipboard" class="flex gap-1 sm:w-2/5">
@ -32,9 +32,9 @@
<section data-controller="modal" data-action="keydown.esc->modal#close"> <section data-controller="modal" data-action="keydown.esc->modal#close">
<h3>Wallet Apps</h3> <h3>Wallet Apps</h3>
<p> <p>
You can connect various wallet apps to your Kosmos account. This allows You can connect various wallet apps to your account. This allows
you to both receive and send sats. Any wallet that supports you to both receive and send sats. Any wallet that supports
<a href="https://bluewallet.io/lndhub/" class="ks-text-link" target="_blank">LNDHub</a> <a href="https://bluewallet.io/lndhub/" target="_blank">LNDHub</a>
accounts should be able to add/import your account using our setup accounts should be able to add/import your account using our setup
code/URL: code/URL:
</p> </p>
@ -57,7 +57,7 @@
<div class="w-full grid grid-cols-1 gap-y-4 md:grid-cols-12 <div class="w-full grid grid-cols-1 gap-y-4 md:grid-cols-12
md:gap-y-6 md:gap-x-4 md:items-center"> md:gap-y-6 md:gap-x-4 md:items-center">
<h4 class="md:col-span-3"> <h4 class="md:col-span-3">
<a href="https://getalby.com/" class="ks-text-link text-xl" <a href="https://getalby.com/" class="text-xl"
title="Alby" target="_blank"> title="Alby" target="_blank">
<%= image_tag("/img/logos/alby.svg", class: 'h-16') %> <%= image_tag("/img/logos/alby.svg", class: 'h-16') %>
</a> </a>
@ -70,7 +70,7 @@
URL in the "LNDHub Export URI" field. URL in the "LNDHub Export URI" field.
</p> </p>
<h4 class="md:col-span-3 mt-4 mb:mt-0"> <h4 class="md:col-span-3 mt-4 mb:mt-0">
<a href="https://bluewallet.io" class="ks-text-link text-xl" <a href="https://bluewallet.io" class="text-xl"
title="Blue Wallet" target="_blank"> title="Blue Wallet" target="_blank">
<%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %> <%= image_tag("/img/logos/bluewallet.svg", class: 'h-16') %>
</a> </a>
@ -83,7 +83,7 @@
then scan the setup QR code. then scan the setup QR code.
</p> </p>
<h4 class="md:col-span-3 mt-4 mb:mt-0"> <h4 class="md:col-span-3 mt-4 mb:mt-0">
<a href="https://zeusln.app" class="ks-text-link text-xl" <a href="https://zeusln.app" class="text-xl"
title="Zeus" target="_blank"> title="Zeus" target="_blank">
<%= image_tag("/img/logos/zeus.svg", class: 'h-16') %> <%= image_tag("/img/logos/zeus.svg", class: 'h-16') %>
</a> </a>

View File

@ -3,7 +3,7 @@
<%= render MainSimpleComponent.new do %> <%= render MainSimpleComponent.new do %>
<section> <section>
<p class="mb-6"> <p class="mb-6">
Follow and interact with anyone on the open social web, from your Kosmos Mastodon account. Follow and interact with anyone on the open social web from your Mastodon account.
</p> </p>
</section> </section>
<section data-controller="modal" data-action="keydown.esc->modal#close"> <section data-controller="modal" data-action="keydown.esc->modal#close">
@ -37,7 +37,7 @@
<p> <p>
Use your Mastodon account with many different apps, and on any devices Use your Mastodon account with many different apps, and on any devices
you wish! When adding your account to an app, you will log in via you wish! When adding your account to an app, you will log in via
<a href="https://kosmos.social" target="_blank" class="ks-text-link">kosmos.social</a>. <a href="https://kosmos.social" target="_blank">kosmos.social</a>.
</p> </p>
</section> </section>
<section> <section>
@ -61,32 +61,32 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <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"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Web Web
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent">
<a href="#" class="bg-white inline-block py-2 px-5 font-semibold no-underline"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Android Android
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
iOS iOS
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Linux Linux
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Windows Windows
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
macOS macOS
</a> </a>
</li> </li>

View File

@ -37,17 +37,15 @@
<h3>Compatible Apps</h3> <h3>Compatible Apps</h3>
<p> <p>
Your Storage account is based on a new open standard called Your Storage account is based on a new open standard called
<a href="https://remotestorage.io" target="_blank"> <img src="/img/logos/icon_remotestorage.svg" class="h-4 w-4 inline">
<img src="/img/logos/icon_remotestorage.svg" class="h-4 w-4 inline"> <strong>remoteStorage</strong>, which is not yet widely supported. Look
<strong>remoteStorage</strong>
</a>, which is not yet widely supported. Look
for the remoteStorage icon, or check the Sync settings in apps. for the remoteStorage icon, or check the Sync settings in apps.
</p> </p>
<p> <p>
If you want your favorite apps to support syncing data with your own If you want your favorite apps to support syncing data with your own
Storage account, let the developers know! All relevant information is Storage account, let the developers know! All relevant information is
available on the <a href="https://remotestorage.io" available on the
target="_blank" class="ks-text-link">remoteStorage website</a>. <a href="https://remotestorage.io" target="_blank">remoteStorage website</a>.
</p> </p>
</section> </section>
@ -67,27 +65,27 @@
</select> </select>
<ul class="hidden sm:flex list-reset mb-8 border-gray-200 border-b"> <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"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Productivity Productivity
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Bookmarks Bookmarks
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Reading Reading
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
File sharing File sharing
</a> </a>
</li> </li>
<li class="mr-2" data-tabs-target="tab" data-action="click->tabs#change:prevent"> <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"> <a href="#" class="bg-white inline-block py-2 px-4 font-semibold no-underline hover:no-underline text-inherit active:text-inherit">
Learning Learning
</a> </a>
</li> </li>

View File

@ -34,7 +34,7 @@
<label class="block"> <label class="block">
<p class="font-bold mb-1">Avatar</p> <p class="font-bold mb-1">Avatar</p>
<p class="text-gray-500">Default profile picture</p> <p class="text-gray-500">Default profile picture</p>
<div class="flex items-center gap-6"> <div class="flex flex-col sm:flex-row items-center gap-6">
<% if @user.avatar.attached? %> <% if @user.avatar.attached? %>
<p class="flex-none"> <p class="flex-none">
<%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %> <%= image_tag image_url_for(@user.avatar), class: "h-24 w-24 rounded-lg" %>

View File

@ -24,7 +24,7 @@
name: "E-Mail", name: "E-Mail",
path: admin_settings_service_path("email"), path: admin_settings_service_path("email"),
text_icon: Setting.email_enabled? ? "◉" : "○", text_icon: Setting.email_enabled? ? "◉" : "○",
active: current_page?(admin_settings_services_path(params: { s: "email" })), active: current_page?(admin_settings_service_path("email")),
) %> ) %>
<%= render SidenavLinkComponent.new( <%= render SidenavLinkComponent.new(
level: 2, level: 2,

View File

@ -1,12 +1,16 @@
<div class="border-b border-gray-200"> <div class="border-b border-gray-200">
<nav class="-mb-px flex" aria-label="Tabs"> <nav class="-mb-px flex" aria-label="Tabs">
<%= render TabnavLinkComponent.new( <%= render TabnavLinkComponent.new(
name: "Donations", path: contributions_donations_path, name: "Donations",
path: contributions_donations_path,
active: current_page?(contributions_donations_path) active: current_page?(contributions_donations_path)
) %> ) %>
<%= render TabnavLinkComponent.new( <%= render TabnavLinkComponent.new(
name: "Projects", path: contributions_projects_path, name: render(EditableContentComponent.new(
active: current_page?(contributions_projects_path) context: "contributions/other", key: "title", default: "Other"
)),
path: contributions_other_path,
active: current_page?(contributions_other_path)
) %> ) %>
</nav> </nav>
</div> </div>

View File

@ -2,7 +2,7 @@
<%= render MainCompactComponent.new do %> <%= render MainCompactComponent.new do %>
<p> <p>
Hey there! You were invited to sign up for a Kosmos account by Hey there! You were invited to sign up for an account by
<strong><%= @invited_by_name %></strong>. <strong><%= @invited_by_name %></strong>.
</p> </p>
<p> <p>

View File

@ -15,6 +15,8 @@ Rails.application.routes.draw do
match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post] match 'signup/:step', to: 'signup#steps', as: :signup_steps, via: [:get, :post]
post 'signup_validate', to: 'signup#validate' post 'signup_validate', to: 'signup#validate'
get 'privacy', to: 'pages#privacy', as: :privacy_page
get 'tos', to: 'pages#tos', as: :tos_page
get "users/:username/avatars/:hash", to: "avatars#show", as: :user_avatar get "users/:username/avatars/:hash", to: "avatars#show", as: :user_avatar
@ -25,7 +27,7 @@ Rails.application.routes.draw do
get 'confirm_btcpay' get 'confirm_btcpay'
end end
end end
get 'projects', to: 'projects#index' get 'other', to: 'other#index'
end end
resources :invitations, only: ['index', 'show', 'create', 'destroy'] resources :invitations, only: ['index', 'show', 'create', 'destroy']
@ -95,12 +97,10 @@ Rails.application.routes.draw do
end end
end end
# post 'users/:username/invitations', to: 'users#create_invitations' resources :donations
resources :editable_contents, except: ['destroy']
get 'invitations', to: 'invitations#index' get 'invitations', to: 'invitations#index'
resources :donations
get 'lightning', to: 'lightning#index' get 'lightning', to: 'lightning#index'
namespace :app_catalog do namespace :app_catalog do

View File

@ -0,0 +1,13 @@
class CreateEditableContents < ActiveRecord::Migration[8.0]
def change
create_table :editable_contents do |t|
t.string :context
t.string :key
t.string :lang, default: "en"
t.text :content
t.boolean :rich_text, default: false
t.timestamps
end
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_05_27_113805) do ActiveRecord::Schema[8.0].define(version: 2025_05_28_092931) do
create_table "active_storage_attachments", force: :cascade do |t| create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false t.string "name", null: false
t.string "record_type", null: false t.string "record_type", null: false
@ -64,6 +64,16 @@ ActiveRecord::Schema[8.0].define(version: 2025_05_27_113805) do
t.index ["user_id"], name: "index_donations_on_user_id" t.index ["user_id"], name: "index_donations_on_user_id"
end end
create_table "editable_contents", force: :cascade do |t|
t.string "context"
t.string "key"
t.string "lang", default: "en"
t.text "content"
t.boolean "rich_text", default: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "flipper_features", force: :cascade do |t| create_table "flipper_features", force: :cascade do |t|
t.string "key", null: false t.string "key", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false