first commit, largely copied volunteers

This commit is contained in:
2023-10-25 22:14:53 +03:00
commit 608b4f6ddf
222 changed files with 5121 additions and 0 deletions

0
app/assets/builds/.keep Normal file
View File

View File

@ -0,0 +1,4 @@
//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../builds

0
app/assets/images/.keep Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
app/assets/images/fb.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1014 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1021 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,18 @@
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's
* vendor/assets/stylesheets directory can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
* compiled file so the styles you add here take precedence over styles defined in any other CSS
* files in this directory. Styles in this file should be added after the last require_* statement.
* It is generally better to create a new file per style scope.
*
*= require_self
*= require glightbox.min
*/
#thredded--container{
max-width: 60rem;
}

View File

@ -0,0 +1,2 @@
@import "tailwind_base";
@import "merged_tailwind_styles";

View File

@ -0,0 +1 @@
.glightbox-container{width:100%;height:100%;position:fixed;top:0;left:0;z-index:999999!important;overflow:hidden;-ms-touch-action:none;touch-action:none;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;-ms-text-size-adjust:100%;text-size-adjust:100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;outline:0}.glightbox-container.inactive{display:none}.glightbox-container .gcontainer{position:relative;width:100%;height:100%;z-index:9999;overflow:hidden}.glightbox-container .gslider{-webkit-transition:-webkit-transform .4s ease;transition:-webkit-transform .4s ease;transition:transform .4s ease;transition:transform .4s ease,-webkit-transform .4s ease;height:100%;left:0;top:0;width:100%;position:relative;overflow:hidden;display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.glightbox-container .gslide{width:100%;position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;opacity:0}.glightbox-container .gslide.current{opacity:1;z-index:99999;position:relative}.glightbox-container .gslide.prev{opacity:1;z-index:9999}.glightbox-container .gslide-inner-content{width:100%}.glightbox-container .ginner-container{position:relative;width:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;max-width:100%;margin:auto;height:100vh}.glightbox-container .ginner-container.gvideo-container{width:100%}.glightbox-container .ginner-container.desc-bottom,.glightbox-container .ginner-container.desc-top{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.glightbox-container .ginner-container.desc-left,.glightbox-container .ginner-container.desc-right{max-width:100%!important}.gslide iframe,.gslide video{outline:0!important;border:none;min-height:165px;-webkit-overflow-scrolling:touch;-ms-touch-action:auto;touch-action:auto}.gslide:not(.current){pointer-events:none}.gslide-image{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.gslide-image img{max-height:100vh;display:block;padding:0;float:none;outline:0;border:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;max-width:100vw;width:auto;height:auto;-o-object-fit:cover;object-fit:cover;-ms-touch-action:none;touch-action:none;margin:auto;min-width:200px}.desc-bottom .gslide-image img,.desc-top .gslide-image img{width:auto}.desc-left .gslide-image img,.desc-right .gslide-image img{width:auto;max-width:100%}.gslide-image img.zoomable{position:relative}.gslide-image img.dragging{cursor:-webkit-grabbing!important;cursor:grabbing!important;-webkit-transition:none;transition:none}.gslide-video{position:relative;max-width:100vh;width:100%!important}.gslide-video .plyr__poster-enabled.plyr--loading .plyr__poster{display:none}.gslide-video .gvideo-wrapper{width:100%;margin:auto}.gslide-video::before{content:'';position:absolute;width:100%;height:100%;background:rgba(255,0,0,.34);display:none}.gslide-video.playing::before{display:none}.gslide-video.fullscreen{max-width:100%!important;min-width:100%;height:75vh}.gslide-video.fullscreen video{max-width:100%!important;width:100%!important}.gslide-inline{background:#fff;text-align:left;max-height:calc(100vh - 40px);overflow:auto;max-width:100%;margin:auto}.gslide-inline .ginlined-content{padding:20px;width:100%}.gslide-inline .dragging{cursor:-webkit-grabbing!important;cursor:grabbing!important;-webkit-transition:none;transition:none}.ginlined-content{overflow:auto;display:block!important;opacity:1}.gslide-external{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;min-width:100%;background:#fff;padding:0;overflow:auto;max-height:75vh;height:100%}.gslide-media{display:-webkit-box;display:-ms-flexbox;display:flex;width:auto}.zoomed .gslide-media{-webkit-box-shadow:none!important;box-shadow:none!important}.desc-bottom .gslide-media,.desc-top .gslide-media{margin:0 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.gslide-description{position:relative;-webkit-box-flex:1;-ms-flex:1 0 100%;flex:1 0 100%}.gslide-description.description-left,.gslide-description.description-right{max-width:100%}.gslide-description.description-bottom,.gslide-description.description-top{margin:0 auto;width:100%}.gslide-description p{margin-bottom:12px}.gslide-description p:last-child{margin-bottom:0}.zoomed .gslide-description{display:none}.glightbox-button-hidden{display:none}.glightbox-mobile .glightbox-container .gslide-description{height:auto!important;width:100%;position:absolute;bottom:0;padding:19px 11px;max-width:100vw!important;-webkit-box-ordinal-group:3!important;-ms-flex-order:2!important;order:2!important;max-height:78vh;overflow:auto!important;background:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,0)),to(rgba(0,0,0,.75)));background:linear-gradient(to bottom,rgba(0,0,0,0) 0,rgba(0,0,0,.75) 100%);-webkit-transition:opacity .3s linear;transition:opacity .3s linear;padding-bottom:50px}.glightbox-mobile .glightbox-container .gslide-title{color:#fff;font-size:1em}.glightbox-mobile .glightbox-container .gslide-desc{color:#a1a1a1}.glightbox-mobile .glightbox-container .gslide-desc a{color:#fff;font-weight:700}.glightbox-mobile .glightbox-container .gslide-desc *{color:inherit}.glightbox-mobile .glightbox-container .gslide-desc .desc-more{color:#fff;opacity:.4}.gdesc-open .gslide-media{-webkit-transition:opacity .5s ease;transition:opacity .5s ease;opacity:.4}.gdesc-open .gdesc-inner{padding-bottom:30px}.gdesc-closed .gslide-media{-webkit-transition:opacity .5s ease;transition:opacity .5s ease;opacity:1}.greset{-webkit-transition:all .3s ease;transition:all .3s ease}.gabsolute{position:absolute}.grelative{position:relative}.glightbox-desc{display:none!important}.glightbox-open{overflow:hidden}.gloader{height:25px;width:25px;-webkit-animation:lightboxLoader .8s infinite linear;animation:lightboxLoader .8s infinite linear;border:2px solid #fff;border-right-color:transparent;border-radius:50%;position:absolute;display:block;z-index:9999;left:0;right:0;margin:0 auto;top:47%}.goverlay{width:100%;height:calc(100vh + 1px);position:fixed;top:-1px;left:0;background:#000;will-change:opacity}.glightbox-mobile .goverlay{background:#000}.gclose,.gnext,.gprev{z-index:99999;cursor:pointer;width:26px;height:44px;border:none;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.gclose svg,.gnext svg,.gprev svg{display:block;width:25px;height:auto;margin:0;padding:0}.gclose.disabled,.gnext.disabled,.gprev.disabled{opacity:.1}.gclose .garrow,.gnext .garrow,.gprev .garrow{stroke:#fff}.gbtn.focused{outline:2px solid #0f3d81}iframe.wait-autoplay{opacity:0}.glightbox-closing .gclose,.glightbox-closing .gnext,.glightbox-closing .gprev{opacity:0!important}.glightbox-clean .gslide-description{background:#fff}.glightbox-clean .gdesc-inner{padding:22px 20px}.glightbox-clean .gslide-title{font-size:1em;font-weight:400;font-family:arial;color:#000;margin-bottom:19px;line-height:1.4em}.glightbox-clean .gslide-desc{font-size:.86em;margin-bottom:0;font-family:arial;line-height:1.4em}.glightbox-clean .gslide-video{background:#000}.glightbox-clean .gclose,.glightbox-clean .gnext,.glightbox-clean .gprev{background-color:rgba(0,0,0,.75);border-radius:4px}.glightbox-clean .gclose path,.glightbox-clean .gnext path,.glightbox-clean .gprev path{fill:#fff}.glightbox-clean .gprev{position:absolute;top:-100%;left:30px;width:40px;height:50px}.glightbox-clean .gnext{position:absolute;top:-100%;right:30px;width:40px;height:50px}.glightbox-clean .gclose{width:35px;height:35px;top:15px;right:10px;position:absolute}.glightbox-clean .gclose svg{width:18px;height:auto}.glightbox-clean .gclose:hover{opacity:1}.gfadeIn{-webkit-animation:gfadeIn .5s ease;animation:gfadeIn .5s ease}.gfadeOut{-webkit-animation:gfadeOut .5s ease;animation:gfadeOut .5s ease}.gslideOutLeft{-webkit-animation:gslideOutLeft .3s ease;animation:gslideOutLeft .3s ease}.gslideInLeft{-webkit-animation:gslideInLeft .3s ease;animation:gslideInLeft .3s ease}.gslideOutRight{-webkit-animation:gslideOutRight .3s ease;animation:gslideOutRight .3s ease}.gslideInRight{-webkit-animation:gslideInRight .3s ease;animation:gslideInRight .3s ease}.gzoomIn{-webkit-animation:gzoomIn .5s ease;animation:gzoomIn .5s ease}.gzoomOut{-webkit-animation:gzoomOut .5s ease;animation:gzoomOut .5s ease}@-webkit-keyframes lightboxLoader{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes lightboxLoader{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes gfadeIn{from{opacity:0}to{opacity:1}}@keyframes gfadeIn{from{opacity:0}to{opacity:1}}@-webkit-keyframes gfadeOut{from{opacity:1}to{opacity:0}}@keyframes gfadeOut{from{opacity:1}to{opacity:0}}@-webkit-keyframes gslideInLeft{from{opacity:0;-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0)}to{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes gslideInLeft{from{opacity:0;-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0)}to{visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes gslideOutLeft{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0);opacity:0;visibility:hidden}}@keyframes gslideOutLeft{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(-60%,0,0);transform:translate3d(-60%,0,0);opacity:0;visibility:hidden}}@-webkit-keyframes gslideInRight{from{opacity:0;visibility:visible;-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@keyframes gslideInRight{from{opacity:0;visibility:visible;-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0)}to{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1}}@-webkit-keyframes gslideOutRight{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0);opacity:0}}@keyframes gslideOutRight{from{opacity:1;visibility:visible;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}to{-webkit-transform:translate3d(60%,0,0);transform:translate3d(60%,0,0);opacity:0}}@-webkit-keyframes gzoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:1}}@keyframes gzoomIn{from{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:1}}@-webkit-keyframes gzoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes gzoomOut{from{opacity:1}50%{opacity:0;-webkit-transform:scale3d(.3,.3,.3);transform:scale3d(.3,.3,.3)}to{opacity:0}}@media (min-width:769px){.glightbox-container .ginner-container{width:auto;height:auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.glightbox-container .ginner-container.desc-top .gslide-description{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.glightbox-container .ginner-container.desc-top .gslide-image,.glightbox-container .ginner-container.desc-top .gslide-image img{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.glightbox-container .ginner-container.desc-left .gslide-description{-webkit-box-ordinal-group:1;-ms-flex-order:0;order:0}.glightbox-container .ginner-container.desc-left .gslide-image{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}.gslide-image img{max-height:97vh;max-width:100%}.gslide-image img.zoomable{cursor:-webkit-zoom-in;cursor:zoom-in}.zoomed .gslide-image img.zoomable{cursor:-webkit-grab;cursor:grab}.gslide-inline{max-height:95vh}.gslide-external{max-height:100vh}.gslide-description.description-left,.gslide-description.description-right{max-width:275px}.glightbox-open{height:auto}.goverlay{background:rgba(0,0,0,.92)}.glightbox-clean .gslide-media{-webkit-box-shadow:1px 2px 9px 0 rgba(0,0,0,.65);box-shadow:1px 2px 9px 0 rgba(0,0,0,.65)}.glightbox-clean .description-left .gdesc-inner,.glightbox-clean .description-right .gdesc-inner{position:absolute;height:100%;overflow-y:auto}.glightbox-clean .gclose,.glightbox-clean .gnext,.glightbox-clean .gprev{background-color:rgba(0,0,0,.32)}.glightbox-clean .gclose:hover,.glightbox-clean .gnext:hover,.glightbox-clean .gprev:hover{background-color:rgba(0,0,0,.7)}.glightbox-clean .gprev{top:45%}.glightbox-clean .gnext{top:45%}}@media (min-width:992px){.glightbox-clean .gclose{opacity:.7;right:20px}}@media screen and (max-height:420px){.goverlay{background:#000}}

View File

@ -0,0 +1,21 @@
.prose {
max-width: 100%;
color: inherit;
--tw-prose-bullets: #6b7280;
--tw-prose-headings: inherit;
}
@layer components {
.button {
@apply inline-block rounded-lg px-3 py-2 text-base font-medium border border-gray-500 hover:border-black;
}
.change {
@apply bg-cyan-200;
}
.remove {
@apply bg-red-200;
}
.action {
@apply bg-green-200;
}
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,18 @@
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
include Pundit::Authorization
alias :current_user :current_member
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
def user_not_authorized
flash[:alert] = "You are not authorized to perform this action."
redirect_back(fallback_location: root_path)
end
end

View File

View File

@ -0,0 +1,59 @@
class MembersController < ApplicationController
before_action :set_member, only: %i[ show edit update destroy ]
# GET /members
def index
@members = Member.public_scope.
page params[:page]
end
def timeline
@members = Member.visible_scope.order(:name).page params[:page]
end
# GET /members/1
def show
end
# GET /members/1/edit
def edit
end
# POST /members
def create
@member = Member.new(member_params)
if @member.save
redirect_to @member, notice: "Member was successfully created."
else
render :new, status: :unprocessable_entity
end
end
# PATCH/PUT /members/1
def update
if @member.update(member_params)
redirect_to @member, notice: "Member was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
# DELETE /members/1
def destroy
@member.destroy
redirect_to members_url, notice: "Member was successfully destroyed."
end
private
# Use callbacks to share common setup or constraints between actions.
def set_member
@member = Member.find(params[:id])
end
# Only allow a list of trusted parameters through.
def member_params
params.require(:member).permit(:name, :public, :bio , :picture,
:picture_cache )
end
end

View File

@ -0,0 +1,56 @@
class PicturesController < ApplicationController
before_action :set_picture, only: %i[ show edit update destroy ]
def index
@q = Picture.ransack(params[:q])
@q.sorts = 'created_at desc' if @q.sorts.empty?
@pictures = @q.result(distinct: true).page( params[:page])
end
def show
end
def new
@picture = Picture.new
end
def edit
authorize @picture
end
def create
@picture = Picture.new(picture_params)
@picture.member = current_member
if @picture.save
redirect_to @picture, notice: "Picture was successfully created."
else
render :new, status: :unprocessable_entity
end
end
def update
authorize @picture
if @picture.update(picture_params)
redirect_to @picture, notice: "Picture was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
authorize @picture
@picture.destroy
redirect_to pictures_url, notice: "Picture was successfully destroyed."
end
private
def set_picture
@picture = Picture.find(params[:id])
end
def picture_params
params.require(:picture).permit(:picture,:picture_cache ,:text,
:happened , :member_id)
end
end

View File

@ -0,0 +1,70 @@
# frozen_string_literal: true
class RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
prepend_before_action :authenticate_scope!, only: [:edit_email]
def new
build_resource
super
end
def create
if message = math_check
puts message
flash.now.alert = message
build_resource(sign_up_params)
render :new
else
super
end
end
def edit_email
build_resource
end
def edit
build_resource
super
end
def update
super
end
protected
def math_check
return "no cheatin" unless bot = params[:bot]
return "No food" unless fudder = bot[:fudder]
key = fudder.to_i / 2
answer = bot[:challenge]
return "Plase enter the bot challenge" if answer.blank?
if( (2*key + 1).to_s != answer )
return "Check the maths, tip, it wasn't #{answer}"
end
nil
end
# If you have extra params to permit, append them to the sanitizer.
# def configure_sign_up_params
# devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
# end
# If you have extra params to permit, append them to the sanitizer.
# def configure_account_update_params
# devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
# end
# The path used after sign up.
def after_sign_up_path_for(resource)
super(resource)
end
# The path used after sign up for inactive accounts.
# def after_inactive_sign_up_path_for(resource)
# super(resource)
# end
end

View File

@ -0,0 +1,55 @@
class StoriesController < ApplicationController
before_action :set_story, only: %i[ show edit update destroy ]
def index
@q = Story.ransack(params[:q])
@q.sorts = 'created_at desc' if @q.sorts.empty?
@stories = @q.result(distinct: true).page( params[:page])
end
def show
end
def new
@story = Story.new
end
def edit
authorize @story
end
def create
@story = Story.new(story_params)
@story.member = current_member
if @story.save
redirect_to @story, notice: "Story was successfully created."
else
render :new, status: :unprocessable_entity
end
end
def update
authorize @story
if @story.update(story_params)
redirect_to @story, notice: "Story was successfully updated."
else
render :edit, status: :unprocessable_entity
end
end
def destroy
authorize @story
@story.destroy
redirect_to stories_url, notice: "Story was successfully destroyed."
end
private
def set_story
@story = Story.find(params[:id])
end
def story_params
params.require(:story).permit(:picture,:picture_cache, :header, :text, :happened)
end
end

View File

@ -0,0 +1,76 @@
require "redcarpet"
module ApplicationHelper
# different template according to the amount of text
def render_story(story)
return "" unless story
puts story.text.length
text_length = story.text.length
template = "text"
template = "half" if text_length < 500
template = "pic" if text_length < 300
render partial: "stories/#{template}" , locals: {story: story}
end
def prose_classes
classes = "prose lg:prose-lg "
classes += "prose-headings:text-inherit "
{ class: classes }
end
def renderer
options = {hard_wrap: true , autolink: true, no_intra_emphasis: true ,
safe_links_only: true, no_styles: true ,
link_attributes: { target: '_blank' }}
html = Redcarpet::Render::HTML.new(options)
Redcarpet::Markdown.new(html, options)
end
def markdown(text)
return "" if text.blank?
text = text.text unless text.is_a?(String)
return "" if text.blank?
self.renderer.render(text).html_safe
end
def shorten(text , to = 100)
return "" if text.blank?
"#{text[0..to]} . . . ".html_safe
end
def main_app
Rails.application.routes.url_helpers
end
def button_classes
"mr-3 inline-block rounded-lg px-3 py-2 text-md font-medium border border-gray-500"
end
def rows( text )
return 5 if text.blank?
text = text.text unless text.is_a?(String)
return 5 if text.blank?
rows = (text.length / 60).to_i
return 5 if rows < 5
rows
end
def main_menu
[["/members" , "Makerspace"],["/stories" , "Stories"], ["/pictures" , "Gallery"],
["/volunteering" , "Volunteering"] ]
end
def member_memu
items =[ [main_app.member_path(current_member) , "Settings"]]
if !Rails.env.production?
items << [merged.pages_path(), "CMS" ]
end
items
end
def mobile_menu
if current_member
member_memu
else
[main_app.member_session_path, "Login"]
end
end
end

View File

@ -0,0 +1,5 @@
# frozen_string_literal: true
module HeroiconHelper
include Heroicon::Engine.helpers
end

View File

@ -0,0 +1,20 @@
module MembersHelper
def picture_for(someone , classes = "")
image = someones_path(someone)
if someone.respond_to? :name
alt = someone.name
else
alt = ""
end
image_tag(image , alt: alt , class: classes )
end
def someones_path( someone )
if someone.picture.blank?
asset_url("no_image.png")
else
someone.picture.url
end
end
end

View File

@ -0,0 +1,2 @@
module PicturesHelper
end

View File

@ -0,0 +1,2 @@
module StoriesHelper
end

View File

@ -0,0 +1,10 @@
// Configure your import map in config/importmap.rb.
// Read more: https://github.com/rails/importmap-rails
// Currently haml filter does not support modules.
// import and make global as workaround
//
// haml module syntax is really clunky, filing issue and hoping
//
import GLightbox from 'glightbox';
globalThis.GLightbox = GLightbox;

View File

@ -0,0 +1,7 @@
class ApplicationJob < ActiveJob::Base
# Automatically retry jobs that encountered a deadlock
# retry_on ActiveRecord::Deadlocked
# Most jobs are safe to ignore if the underlying records are no longer available
# discard_on ActiveJob::DeserializationError
end

View File

@ -0,0 +1,4 @@
class ApplicationMailer < ActionMailer::Base
default from: "from@example.com"
layout "mailer"
end

View File

@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
primary_abstract_class
end

View File

35
app/models/member.rb Normal file
View File

@ -0,0 +1,35 @@
class Member < ApplicationRecord
after_create :skip_conf!
def self.public_scope
where.not(confirmed_at: nil).where.not(picture: nil)
end
def self.visible_scope
where.not(confirmed_at: nil)
end
# Include default devise modules. Others available are:
# , :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,:confirmable,
:recoverable, :rememberable, :validatable, :async
def skip_conf!
self.confirm if Rails.env.development?
end
mount_uploader :picture, PictureUploader
has_many :stories
has_many :stories
has_many :pictures
validates :bio, length: { maximum: 1000 }
validates :name , length: { minimum: 3 }
def admin
self.email == "torsten@villataika.fi"
end
def admin?
self.email == "torsten@villataika.fi"
end
end

10
app/models/picture.rb Normal file
View File

@ -0,0 +1,10 @@
class Picture < ApplicationRecord
belongs_to :member
mount_uploader :picture, PictureUploader
validates :text, length: { maximum: 80 }
validates :happened, presence: true
validates :picture, presence: true
end

13
app/models/story.rb Normal file
View File

@ -0,0 +1,13 @@
class Story < ApplicationRecord
belongs_to :member
mount_uploader :picture, PictureUploader
validates :text, length: { minimum: 5 , maximum: 1000 }
validates :header , length: { minimum: 5 , maximum: 400}
validates :picture, presence: true
def name
header
end
end

View File

@ -0,0 +1,53 @@
# frozen_string_literal: true
class ApplicationPolicy
attr_reader :member, :record
def initialize(member, record)
@member = member
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
class Scope
def initialize(member, scope)
@member = member
@scope = scope
end
def resolve
raise NotImplementedError, "You must define #resolve in #{self.class}"
end
private
attr_reader :member, :scope
end
end

View File

@ -0,0 +1,14 @@
# allows to edit/detroy own data
# which can be viewed by anyone
class EditOwnPolicy < ApplicationPolicy
def edit?
return true if member.admin?
owner?
end
def owner?
member == record.member
end
alias :update? :edit?
alias :destroy? :edit?
end

View File

@ -0,0 +1,3 @@
class PicturePolicy < EditOwnPolicy
end

View File

@ -0,0 +1,3 @@
class StoryPolicy < EditOwnPolicy
end

View File

@ -0,0 +1,43 @@
class PictureUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# include CarrierWave::RMagick
include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :file
# storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url(*args)
# For Rails 3.1+ asset pipeline compatibility:
ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
end
# Process files as they are uploaded:
# process scale: [200, 300]
#
# def scale(width, height)
# # do something
# end
# Create different versions of your uploaded files:
# version :thumb do
# process resize_to_fit: [50, 50]
# end
def content_type_allowlist
/image\//
end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
# "something.jpg" if original_filename
# end
end

View File

@ -0,0 +1,20 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h1.font-hairline.mb-6.text-center Resend Confirmation Instructions
= form_for(resource, as: resource_name,
url: confirmation_path(resource_name),
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" ,
method: :post }) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.email_field :email, |
autofocus: true, |
autocomplete: "email", |
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), |
class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline" |
.mb-4
= f.submit "Resend Confirmation Info", |
class: "button bg-cyan-700 hover:bg-cyan-500 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -0,0 +1,14 @@
%p
Welcome #{@resource.name}!
%p You can confirm your stay through the link below:
%p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token)
Remember you can change the dates later, under the Settings.
You can also set a profile and picture to tell others about yourself.
And once at the Hub, you can add pictures and stories to share with others.
Looking forward to seeing you in person
The hub team

View File

@ -0,0 +1,8 @@
%p
Hello #{@email}!
- if @resource.try(:unconfirmed_email?)
%p
We're contacting you to notify you that your email is being changed to #{@resource.unconfirmed_email}.
- else
%p
We're contacting you to notify you that your email has been changed to #{@resource.email}.

View File

@ -0,0 +1,3 @@
%p
Hello #{@resource.email}!
%p We're contacting you to notify you that your password has been changed.

View File

@ -0,0 +1,6 @@
%p
Hello #{@resource.email}!
%p Someone has requested a link to change your password. You can do this through the link below.
%p= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token)
%p If you didn't request this, please ignore this email.
%p Your password won't change until you access the link above and create a new one.

View File

@ -0,0 +1,5 @@
%p
Hello #{@resource.email}!
%p Your account has been locked due to an excessive number of unsuccessful sign in attempts.
%p Click the link below to unlock your account:
%p= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token)

View File

@ -0,0 +1,32 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h2.font-hairline.mb-6.text-center Change Your Password
= form_for(resource,
as: resource_name,
html: { method: :put, class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" },
url: password_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
= f.hidden_field :reset_password_token
.mb-4
= f.label :password, "New Password",
class: "block font-bold mb-2 text-gray-700 text-sm"
- if @minimum_password_length
%small
%em.text-gray-600
(#{@minimum_password_length} characters minimum)
= f.password_field :password, |
autofocus: true, |
autocomplete: "new-password", |
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full"
.mb-4
= f.label :password_confirmation,
"Confirm New Password",
class: "block font-bold mb-2 text-gray-700 text-sm"
= f.password_field :password_confirmation, |
autocomplete: "off", |
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
.mb-4
= f.submit "Change My Password", |
class: "button bg-cyan-700 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -0,0 +1,17 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h1.font-hairline.mb-6.text-center Forgot your password?
= form_for(resource, as: resource_name,
html: { method: :post,
class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" } ,
url: password_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.email_field :email, autofocus: true, autocomplete: "email", |
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" |
.mb-4
= f.submit "Send Password Reset Info", |
class: "button bg-cyan-700 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -0,0 +1,32 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h1.font-hairline.mb-6.text-center.text-2xl
Change password
= form_for(resource, as: resource_name,
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md",
method: :put } ,
url: registration_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
- if devise_mapping.confirmable? && resource.pending_reconfirmation?
%div
Currently waiting confirmation for: #{resource.unconfirmed_email}
.mb-4
= f.label :new_password, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.password_field :password, autocomplete: "new-password", class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
.mb-4
= f.label :password_confirmation, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.password_field :password_confirmation, |
autocomplete: "new-password", |
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" |
.mb-4
= f.label :current_password, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.password_field :current_password, |
autocomplete: "current-password", |
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" |
.actions
= f.submit "Update", class: "button bg-cyan-700 hover:bg-cyan-500 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full"
.flex.justify-between
%p
%span= button_to "Delete my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete , class: button_classes
%button{class: button_classes}
= link_to "Back", :back

View File

@ -0,0 +1,32 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h1.font-hairline.mb-6.text-center.text-2xl
Change your email
.text-center.text-lg
(requires confirmation)
= form_for(resource, as: resource_name,
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md",
method: :put } ,
url: registration_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :new_email, class: "block font-bold mb-2 text-gray-700 text-sm"
%br/
= f.email_field :email, |
autofocus: true, |
autocomplete: "email", |
class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline" |
- if devise_mapping.confirmable? && resource.pending_reconfirmation?
%div
Currently waiting confirmation for: #{resource.unconfirmed_email}
.mb-4
= f.label :current_password, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.password_field :current_password, |
autocomplete: "current-password", |
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline" |
.actions
= f.submit "Update", class: "button bg-cyan-700 hover:bg-cyan-500 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full"
= link_to :back do
%button{class: button_classes}
Back

View File

@ -0,0 +1,51 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h1.font-hairline.mb-6.text-center.text-2xl Confirm your stay
= form_for(resource, as: resource_name,
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" } ,
url: registration_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
Please read the
= link_to "house rules" , "/house_rules" , class: "underline"
if you haven't yet, and let us know of
any alergies (food?) or medical conditions (ADHD?) beforehand.
.mb-4
= f.label :name, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.input :name,
placeholder: "Pekka",
class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline"
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.email_field :email,
autocomplete: "email",
placeholder: "user@example.com",
class: "appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none shadow focus:shadow-outline"
.mb-4
= f.label :password, class: "block font-bold mb-2 text-gray-700 text-sm"
- if @minimum_password_length
%small
%em.text-gray-600
(#{@minimum_password_length} characters minimum)
= f.password_field :password,
autocomplete: "new-password",
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
.mb-4
= f.label :password_confirmation, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.password_field :password_confirmation,
autocomplete: "new-password",
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full"
.mt-4
- challenge = rand(8)
= simple_fields_for :bot do |n|
= n.input :fudder , as: :hidden , input_html: { value: "#{challenge*2}" }
= n.input :challenge, placeholder: "Anti bot question: #{challenge} + #{challenge + 1} == " ,
class: " border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full"
.my-4
= f.submit "Sign Up",
class: "button bg-cyan-700 hover:bg-cyan-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full"
%p
After submitting you will receive a confirmation email.
Your place will be ensured once you have confirmed your email.
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -0,0 +1,26 @@
.flex.justify-center
.w-full.max-w-xs.md:max-w-md
%h1.font-hairline.mb-6.text-center Log In
= form_for(resource, url: session_path(resource_name),
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" },
as: resource_name ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :email, class: "block text-gray-700 text-sm font-bold mb-2"
= f.email_field :email, autofocus: true, autocomplete: "email",
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight foucs:outline-none focus:shadow-outline"
.mb-4
= f.label :password, class: "block text-gray-700 text-sm font-bold mb-2"
= f.password_field :password,
autocomplete: "current-password",
class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
- if devise_mapping.rememberable?
.mb-4
= f.check_box :remember_me, class: "mr-2 leading-tight"
= f.label :remember_me,
class: "align-baseline inline-block text-gray-700 text-sm"
.mb-4
= f.submit "Log in",
class: "button bg-cyan-700 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline w-full"
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -0,0 +1,9 @@
- if resource.errors.any?
#error_explanation
%h2.bg-red-100.border-l-4.border-red-500.mb-4.p-4.text-red-700.font-bold
= I18n.t("errors.messages.not_saved",
count: resource.errors.count,
resource: resource.class.model_name.human.downcase)
%ul
- resource.errors.full_messages.each do |message|
%li= message

View File

@ -0,0 +1,2 @@
%p.text-center.text-gray-500.text-xs
Be a member of Hub Feenix

View File

@ -0,0 +1,20 @@
- if controller_name != 'sessions'
= link_to "Log in", new_session_path(resource_name), |
class: "inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" |
%br/
- if devise_mapping.registerable? && controller_name != 'registrations'
= link_to "Sign up", new_registration_path(resource_name), |
class: "inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" |
%br/
- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations'
= link_to "Forgot Password?", new_password_path(resource_name), |
class: "inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" |
%br/
- if devise_mapping.confirmable? && controller_name != 'confirmations'
= link_to "Didn't receive confirmation info?", new_confirmation_path(resource_name), |
class: "inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" |
%br/
- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
= link_to "Didn't receive unlock info?", new_unlock_path(resource_name), |
class: "inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800" |
%br/

View File

@ -0,0 +1,19 @@
.flex.justify-center
.w-full.max-w-xs
%h1.font-hairline.mb-6.text-center Resend Unlock Info
= form_for(resource, as: resource_name,
html: { class: "bg-white mb-4 px-8 pt-6 pb-8 rounded shadow-md" ,
method: :post }
url: unlock_path(resource_name) ) do |f|
= render "devise/shared/error_messages", resource: resource
.mb-4
= f.label :email, class: "block font-bold mb-2 text-gray-700 text-sm"
= f.email_field :email, |
autofocus: true, |
autocomplete: "email", |
class: "appearance-none border leading-tight focus:outline-none px-3 py-2 rounded shadow focus:shadow-outline text-gray-700 w-full" |
.mb-4
= f.submit "Resend unlock instructions", |
class: "button bg-blue-500 hover:bg-blue-700 font-bold text-white focus:outline-none py-2 px-4 rounded focus:shadow-outline w-full" |
= render "devise/shared/links"
= render "devise/shared/form_footer"

View File

@ -0,0 +1,9 @@
-# Link to the "First" page
-# available local variables
-# url: url to the first page
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%span.first
= link_to_unless current_page.first?, t('views.pagination.first').html_safe, url, remote: remote

View File

@ -0,0 +1,8 @@
-# Non-link tag that stands for skipped pages...
-# available local variables
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%span.page.gap
= t('views.pagination.truncate').html_safe

View File

@ -0,0 +1,9 @@
-# Link to the "Last" page
-# available local variables
-# url: url to the last page
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%span.last
= link_to_unless current_page.last?, t('views.pagination.last').html_safe, url, remote: remote

View File

@ -0,0 +1,9 @@
-# Link to the "Next" page
-# available local variables
-# url: url to the next page
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%span.next
= link_to_unless current_page.last?, t('views.pagination.next').html_safe, url, rel: 'next', remote: remote

View File

@ -0,0 +1,10 @@
-# Link showing page number
-# available local variables
-# page: a page object for "this" page
-# url: url to this page
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%span{class: "page#{' current' if page.current?}"}
= link_to_unless page.current?, page, url, {remote: remote, rel: page.rel}

View File

@ -0,0 +1,18 @@
-# The container tag
-# available local variables
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
-# paginator: the paginator that renders the pagination tags inside
= paginator.render do
%nav.pagination
= first_page_tag unless current_page.first?
= prev_page_tag unless current_page.first?
- each_page do |page|
- if page.display_tag?
= page_tag page
- elsif !page.was_truncated?
= gap_tag
= next_page_tag unless current_page.last?
= last_page_tag unless current_page.last?

View File

@ -0,0 +1,9 @@
-# Link to the "Previous" page
-# available local variables
-# url: url to the previous page
-# current_page: a page object for the currently displayed page
-# total_pages: total number of pages
-# per_page: number of items to fetch per page
-# remote: data-remote
%span.prev
= link_to_unless current_page.first?, t('views.pagination.previous').html_safe, url, rel: 'prev', remote: remote

View File

@ -0,0 +1,112 @@
%footer.bg-white{"aria-label" => "Site Footer"}
.px-4.py-16.mx-auto.sm:px-6.lg:px-8
.w-full.grid.grid-cols-2.gap-8.mt-8.lg:mt-0.lg:grid-cols-6.lg:gap-y-16.ld:grid-cols-3.lg:gap-y-8
.max-w-screen-xl.px-4.py-16.mx-auto.sm:px-6.lg:px-8
.lg:flex.lg:items-start.lg:gap-8
%a.inline-flex.items-center{"aria-label" => "Hub Feenix", :href => "/", :title => "Hub Feenix"}
= image_tag("feenix_lintu.webp" , class: "h-20")
%nav.mt-6
%p.mb-6.font-medium.text-xl.text-gray-900
Activities
%ul.space-y-4.text-sm
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/local_activities"}
Local Area
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/helsinki"}
Helsinki
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/turku"}
Turkku
%nav.mt-6
%p.mb-6.font-medium.text-xl.text-gray-900
Weather Info
%ul.space-y-4.text-sm
%li
%a.text-md.transition.hover:opacity-75{:href => "/four_seasons"}
Four Seasons
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/in_summer"}
In Summer
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/in_winter"}
In Winter
%nav.mt-6{"aria-label" => "Footer Navigation - Hub Feenix"}
%p.mb-6.font-medium.text-xl.text-gray-900
Volunteering
%ul.space-y-4.text-sm
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/volunteering"}
About Volunteering
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/arriving"}
Getting here
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/living_here"}
Living @ Feenix Info
%nav.mt-6{"aria-label" => "Footer Navigation - Hub Feenix"}
%p.mb-6.font-medium.text-xl.text-gray-900
People
%ul.space-y-4.text-sm
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/members"}
Makerspace
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/stories"}
Stories
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/pictures"}
Gallery
%nav.mt-6{"aria-label" => "Footer Navigation - Downloads"}
%p.mb-6.font-medium.text-xl.text-gray-900
Information
%ul.space-y-4.text-sm
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "/about"}
About Hub Feenix
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "https://www.hubfeenix.fi" , target: :blank }
Hub Feenix Website
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "https://www.facebook.com/hubfeenix" , :target => "_blank"}
Facebook
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "https://www.instagram.com/hub_feenix/" , :target => "_blank"}
Instagram
%ul.flex.justify-start.col-span-2.gap-6.lg:col-span-5.lg:justify-end
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "https://www.facebook.com/hubfeenix", :rel => "noreferrer", :target => "_blank"}
%span.sr-only Facebook
%svg.w-6.h-6{"aria-hidden" => "true", :fill => "currentColor", :viewBox => "0 0 24 24"}
%path{"clip-rule" => "evenodd", :d => "M22 12c0-5.523-4.477-10-10-10S2 6.477 2 12c0 4.991 3.657 9.128 8.438 9.878v-6.987h-2.54V12h2.54V9.797c0-2.506 1.492-3.89 3.777-3.89 1.094 0 2.238.195 2.238.195v2.46h-1.26c-1.243 0-1.63.771-1.63 1.562V12h2.773l-.443 2.89h-2.33v6.988C18.343 21.128 22 16.991 22 12z", "fill-rule" => "evenodd"}
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "https://www.instagram.com/hub_feenix/", :rel => "noreferrer", :target => "_blank"}
%span.sr-only Instagram
%svg.w-6.h-6{"aria-hidden" => "true", :fill => "currentColor", :viewBox => "0 0 24 24"}
%path{"clip-rule" => "evenodd", :d => "M12.315 2c2.43 0 2.784.013 3.808.06 1.064.049 1.791.218 2.427.465a4.902 4.902 0 011.772 1.153 4.902 4.902 0 011.153 1.772c.247.636.416 1.363.465 2.427.048 1.067.06 1.407.06 4.123v.08c0 2.643-.012 2.987-.06 4.043-.049 1.064-.218 1.791-.465 2.427a4.902 4.902 0 01-1.153 1.772 4.902 4.902 0 01-1.772 1.153c-.636.247-1.363.416-2.427.465-1.067.048-1.407.06-4.123.06h-.08c-2.643 0-2.987-.012-4.043-.06-1.064-.049-1.791-.218-2.427-.465a4.902 4.902 0 01-1.772-1.153 4.902 4.902 0 01-1.153-1.772c-.247-.636-.416-1.363-.465-2.427-.047-1.024-.06-1.379-.06-3.808v-.63c0-2.43.013-2.784.06-3.808.049-1.064.218-1.791.465-2.427a4.902 4.902 0 011.153-1.772A4.902 4.902 0 015.45 2.525c.636-.247 1.363-.416 2.427-.465C8.901 2.013 9.256 2 11.685 2h.63zm-.081 1.802h-.468c-2.456 0-2.784.011-3.807.058-.975.045-1.504.207-1.857.344-.467.182-.8.398-1.15.748-.35.35-.566.683-.748 1.15-.137.353-.3.882-.344 1.857-.047 1.023-.058 1.351-.058 3.807v.468c0 2.456.011 2.784.058 3.807.045.975.207 1.504.344 1.857.182.466.399.8.748 1.15.35.35.683.566 1.15.748.353.137.882.3 1.857.344 1.054.048 1.37.058 4.041.058h.08c2.597 0 2.917-.01 3.96-.058.976-.045 1.505-.207 1.858-.344.466-.182.8-.398 1.15-.748.35-.35.566-.683.748-1.15.137-.353.3-.882.344-1.857.048-1.055.058-1.37.058-4.041v-.08c0-2.597-.01-2.917-.058-3.96-.045-.976-.207-1.505-.344-1.858a3.097 3.097 0 00-.748-1.15 3.098 3.098 0 00-1.15-.748c-.353-.137-.882-.3-1.857-.344-1.023-.047-1.351-.058-3.807-.058zM12 6.865a5.135 5.135 0 110 10.27 5.135 5.135 0 010-10.27zm0 1.802a3.333 3.333 0 100 6.666 3.333 3.333 0 000-6.666zm5.338-3.205a1.2 1.2 0 110 2.4 1.2 1.2 0 010-2.4z", "fill-rule" => "evenodd"}
%li
%a.text-gray-700.transition.hover:opacity-75{:href => "https://github.com/FeenixMakers", :rel => "noreferrer", :target => "_blank"}
%span.sr-only GitHub
%svg.w-6.h-6{"aria-hidden" => "true", :fill => "currentColor", :viewBox => "0 0 24 24"}
%path{"clip-rule" => "evenodd", :d => "M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z", "fill-rule" => "evenodd"}
.pt-8.mt-8.border-t.border-gray-100
.grid.grid-cols-1.gap-8.lg:grid-cols-2
%p.text-xs.text-left.text-gray-500
2020-23. Osuuskunta Hub Feenix. All rights reserved.
%nav{"aria-label" => "Footer Navigation - Support"}
%ul.flex.flex-wrap.justify-start.gap-4.text-xs.lg:justify-end
%li
%a.text-gray-500.transition.hover:opacity-75{:href => "#"}
=#Terms Conditions
%li
%a.text-gray-500.transition.hover:opacity-75{:href => "#"}
=#Privacy Policy
%li
%a.text-gray-500.transition.hover:opacity-75{:href => "#"}
=#Cookies

View File

@ -0,0 +1,38 @@
%header.px-4.py-5.mx-auto.sm:max-w-xl.md:max-w-full.lg:max-w-screen-xl.md:px-24.lg:px-8
.relative.flex.items-center.justify-between
%a.inline-flex.items-center{"aria-label" => "Hub Feenix", :href => "/", :title => "Hub Feenix"}
= image_tag("feenix_lintu.webp" , class: "h-20")
%span.ml-2.text-xl.font-bold.text-gray-800 Makerspace @ Hub Feenix
%ul.flex.items-center.hidden.space-x-8.lg:flex
- main_menu.each do |link , text|
%li
%a.font-medium.tracking-wide.text-gray-700.transition-colors.duration-400.hover:text-cyan-800{"aria-label" => "Our product", :href => link, :title => text}= text
%li
= link_to "https://www.facebook.com/hubfeenix" , :target => "_blank" do
= image_tag("fb.webp" , class: "h-10 rounded-md")
%li
- unless member_signed_in?
%a.inline-flex.items-center.justify-center.h-12.px-6.font-medium.tracking-wide.text-white.transition.duration-200.rounded-lg.shadow-md.bg-green-800.hover:bg-blue-800.focus:shadow-outline.focus:outline-none{"aria-label" => "Sign up", :href => main_app.member_session_path, :title => "Log in or Sign up"}
Login
- else
.inline-flex.items-stretch.rounded-md.border
.rounded-l-md.px-4.py-2.text-sm.text-gray-600.hover:bg-cyan-100.hover:text-gray-900
=link_to current_member.email , main_app.member_path(current_member)
.relative
%button.inline-flex.h-full.items-center.justify-center.rounded-r-md.border-l.border-gray-100.px-2.text-gray-600.hover:bg-cyan-400{:type => "button" , onclick: "dropdown();" }
%span.sr-only Menu
%svg.h-4.w-4{:fill => "currentColor", :viewbox => "0 0 20 20", :xmlns => "http://www.w3.org/2000/svg"}
%path{"clip-rule" => "evenodd", :d => "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", "fill-rule" => "evenodd"}
=render "layouts/member_menu"
.lg:hidden.flex.items-center.justify-between
.mr-20
= link_to "https://www.facebook.com/hubfeenix" , :target => "_blank" do
= image_tag("fb.webp" , class: "h-10 rounded-md")
%button.p-2.-mr-1.transition.duration-200.rounded.focus:outline-none.focus:shadow-outline.hover:bg-deep-purple-50.focus:bg-deep-purple-50{"aria-label" => "Open Menu", :title => "Open Menu" , onclick: "menu_on();"}
%svg.w-5.text-gray-600{:viewbox => "0 0 24 24"}
%path{:d => "M23,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h22c0.6,0,1,0.4,1,1S23.6,13,23,13z", :fill => "currentColor"}
%path{:d => "M23,6H1C0.4,6,0,5.6,0,5s0.4-1,1-1h22c0.6,0,1,0.4,1,1S23.6,6,23,6z", :fill => "currentColor"}
%path{:d => "M23,20H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h22c0.6,0,1,0.4,1,1S23.6,20,23,20z", :fill => "currentColor"}
= render "layouts/mobile_menu"

View File

@ -0,0 +1,23 @@
#menu-dropdown.hidden.absolute.right-0.z-10.mt-4.w-36.origin-top-right.rounded-md.border.border-gray-100.bg-white.shadow-lg{:role => "menu"}
.p-2
- member_memu.each do |link , text|
%a.block.rounded-lg.px-4.py-2.text-sm.text-gray-500.hover:bg-gray-50.hover:text-gray-700{:href => link, :role => "menuitem"}
=text
= form_tag( main_app.destroy_member_session_path , {method: :delete } ) do
%button.flex.w-full.items-center.gap-2.rounded-lg.px-4.py-2.text-sm.text-blue-700.hover:bg-red-50{:role => "menuitem", :type => "submit"}
%svg.h-4.w-4{:fill => "none", :stroke => "currentColor", "stroke-width" => "2", :viewbox => "0 0 24 24", :xmlns => "http://www.w3.org/2000/svg"}
%path{:d => "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16", "stroke-linecap" => "round", "stroke-linejoin" => "round"}
Sign out
:javascript
var drop_hidden = true;
function dropdown(){
var x = document.getElementById("menu-dropdown");
if( drop_hidden == true){
x.style.display = "block";
drop_hidden = false ;
} else {
x.style.display = "none";
drop_hidden = true ;
}
}

View File

@ -0,0 +1,28 @@
-if flash.alert
#flash
.m-20.rounded.border-l-4.border-red-500.bg-red-50.p-4{:role => "alert"}
%strong.block.font-medium.text-red-700 Oops
%p.mt-2.text-sm.text-red-700
=flash.alert
-if flash.notice
#flash
.m-20.rounded-xl.border.border-gray-100.p-4.shadow-xl{:role => "alert"}
.flex.items-start.gap-4
%span.text-green-600
%svg.h-6.w-6{:fill => "none", :stroke => "currentColor", "stroke-width" => "1.5", :viewbox => "0 0 24 24", :xmlns => "http://www.w3.org/2000/svg"}
%path{:d => "M9 12.75L11.25 15 15 9.75M21 12a9 9 0 11-18 0 9 9 0 0118 0z", "stroke-linecap" => "round", "stroke-linejoin" => "round"}
.flex-1
%strong.block.font-medium.text-gray-900 Ok
%p.mt-1.text-sm.text-gray-700
= flash.notice
:javascript
function hideNotice() {
const notification = document.querySelector('#flash')
if (notification) {
setInterval(function() {
notification.classList.add('hidden');
}, 5000);
}
}
hideNotice();

View File

@ -0,0 +1,42 @@
#mobile-menu.absolute.top-0.leaving-0.w-full.hidden.z-10
.p-5.bg-white.border.rounded.shadow-sm
.flex.items-center.justify-between.mb-4
%div
%a.inline-flex.items-center{"aria-label" => "Hub Feenix", :href => "/", :title => "Hub Feenix"}
= image_tag("feenix_lintu.webp" , class: "h-20")
%span.ml-2.text-xl.font-bold.tracking-wide.text-gray-800.uppercase Hub Feenix
%div
%button.p-2.-mt-2.-mr-2.transition.duration-200.rounded.hover:bg-gray-200.focus:bg-gray-200.focus:outline-none.focus:shadow-outline{"aria-label" => "Close Menu", :title => "Close Menu" , onclick: "menu_off();"}
%svg.w-5.text-gray-600{:viewbox => "0 0 24 24"}
%path{:d => "M19.7,4.3c-0.4-0.4-1-0.4-1.4,0L12,10.6L5.7,4.3c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l6.3,6.3l-6.3,6.3 c-0.4,0.4-0.4,1,0,1.4C4.5,19.9,4.7,20,5,20s0.5-0.1,0.7-0.3l6.3-6.3l6.3,6.3c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3 c0.4-0.4,0.4-1,0-1.4L13.4,12l6.3-6.3C20.1,5.3,20.1,4.7,19.7,4.3z", :fill => "currentColor"}
%nav
%ul.space-y-6.px-20.w-lg.text-center
- main_menu.each do |link , text|
%li
%a.w-full.h-full.block.tracking-wide.text-xl.p-2.rounded-lg.hover:bg-cyan-200{ :href => link , :title => text}
= text
%li
%hr
- mobile_menu.each do |link , text|
%li
%a.w-full.h-full.block.tracking-wide.text-xl.p-2.rounded-lg.hover:bg-cyan-200{ :href => link , :title => text}
= text
-if(current_member)
= form_tag( main_app.destroy_member_session_path , {method: :delete ,class: "text-center"}) do
%button.w-full.items-center.gap-2.rounded-lg.px-4.py-2.text-sm.text-blue-700.hover:bg-red-50{:role => "menuitem", :type => "submit"}
%svg.h-4.w-4{:fill => "none", :stroke => "currentColor", "stroke-width" => "2", :viewbox => "0 0 24 24", :xmlns => "http://www.w3.org/2000/svg"}
%path{:d => "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16", "stroke-linecap" => "round", "stroke-linejoin" => "round"}
Sign out
-else
%a.inline-flex.items-center.justify-center.h-12.px-6.font-medium.tracking-wide.text-white.transition.duration-200.rounded-lg.shadow-md.bg-green-800.hover:bg-blue-800.focus:shadow-outline.focus:outline-none{"aria-label" => "Sign up", :href => main_app.member_session_path, :title => "Log in or Sign up"}
Login
:javascript
var drop_hidden = true;
function menu_on() {
var x = document.getElementById("mobile-menu");
x.style.display = "block";
}
function menu_off() {
var x = document.getElementById("mobile-menu");
x.style.display = "none";
}

View File

@ -0,0 +1,39 @@
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
%title Hubfeenix Makerspace
%meta{:content => "width=device-width,initial-scale=1", :name => "viewport"}/
// https://www.favicon-generator.org/
%link{:href => "/apple-icon-57x57.png", :rel => "apple-touch-icon", :sizes => "57x57"}/
%link{:href => "/apple-icon-60x60.png", :rel => "apple-touch-icon", :sizes => "60x60"}/
%link{:href => "/apple-icon-72x72.png", :rel => "apple-touch-icon", :sizes => "72x72"}/
%link{:href => "/apple-icon-76x76.png", :rel => "apple-touch-icon", :sizes => "76x76"}/
%link{:href => "/apple-icon-114x114.png", :rel => "apple-touch-icon", :sizes => "114x114"}/
%link{:href => "/apple-icon-120x120.png", :rel => "apple-touch-icon", :sizes => "120x120"}/
%link{:href => "/apple-icon-144x144.png", :rel => "apple-touch-icon", :sizes => "144x144"}/
%link{:href => "/apple-icon-152x152.png", :rel => "apple-touch-icon", :sizes => "152x152"}/
%link{:href => "/apple-icon-180x180.png", :rel => "apple-touch-icon", :sizes => "180x180"}/
%link{:href => "/android-icon-192x192.png", :rel => "icon", :sizes => "192x192", :type => "image/png"}/
%link{:href => "/favicon-32x32.png", :rel => "icon", :sizes => "32x32", :type => "image/png"}/
%link{:href => "/favicon-96x96.png", :rel => "icon", :sizes => "96x96", :type => "image/png"}/
%link{:href => "/favicon-16x16.png", :rel => "icon", :sizes => "16x16", :type => "image/png"}/
%link{:href => "/manifest.json", :rel => "manifest"}/
%meta{:content => "#ffffff", :name => "msapplication-TileColor"}/
%meta{:content => "/ms-icon-144x144.png", :name => "msapplication-TileImage"}/
%meta{:content => "#ffffff", :name => "theme-color"}/
= csrf_meta_tags
= csp_meta_tag
= stylesheet_link_tag "tailwind"
= stylesheet_link_tag "application"
= javascript_importmap_tags
- if false
%script{:src => "https://cdn.tailwindcss.com"}
%body.xl:mx-auto{class: "max-w-[1920px]"}
= render "layouts/header"
= render "layouts/messages"
= yield
= render "layouts/footer"

View File

@ -0,0 +1,8 @@
!!!
%html
%head
%meta{:content => "text/html; charset=utf-8", "http-equiv" => "Content-Type"}/
:css
/* Email styles need to be inline */
%body
= yield

View File

@ -0,0 +1 @@
= yield

View File

@ -0,0 +1,23 @@
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
%title
Forum #{yield :thredded_page_title}
%meta{:content => "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0", :name => "viewport"}/
= stylesheet_link_tag "tailwind"
= stylesheet_link_tag 'thredded', 'data-turbolinks-track': 'reload'
= stylesheet_link_tag "application"
= csrf_meta_tag
= csp_meta_tag
= javascript_include_tag 'thredded', |
async: !Rails.application.config.assets.debug, |
defer: true, |
'data-turbolinks-track': 'reload' |
= RailsGravatar.prefetch_dns_tag
%meta{:content => "width=device-width, initial-scale=1, user-scalable=no", :name => "viewport"}/
%body
= render "layouts/header"
= render "layouts/messages"
= yield
= render "layouts/footer"

View File

@ -0,0 +1,26 @@
%script{:src => "https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"}
%script{:src => "https://cdn.jsdelivr.net/npm/marked/marked.min.js"}
.flex.justify-center.m-5.m-5.md:m-12.lg:m-20
.flex.flex-col.text-center
%h1.text-4xl Edit your profile
.flex.justify-center.m-5.m-5.md:m-12.lg:m-20
.flex.flex-col.text-center{class: "w-full md:w-10/12"}
The Picture box is landscape with ratio 3/4.
= simple_form_for @member do |f|
.grid.grid-cols-1.md:grid-cols-2.gap-10
= render "merged/form/editor" , object: @member , field: :bio, form: f
.info.mr-8
.text-red-700= f.error_notification
= f.input :name
.flex.h-16.mt-2.col-span-2
= image_tag(@member.picture_url , class: "align-middle mr-4") if @member.picture?
.w-full= f.input :picture , as: :file ,
label: (@member.picture.blank? ? "Add picture" : "Change picture")
= f.hidden_field :picture_cache
.flex.justify-center.actions.m-10
= f.button :button, "Update", class: button_classes + " bg-cyan-200"
= link_to member_path(@member) do
%button.ml-10{type: :submit, class: button_classes}
Back

View File

@ -0,0 +1,20 @@
= paginate @members
.flex.justify-center.m-8.mx-5.md:mx-12.lg:mx-20
.flex.flex-col.text-center
%h1.text-4xl Current Makerspace at Hub Feenix
.mx-20.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.2xl:grid-cols-4.gap-8.md:gap-12.lg:gap-16
- @members.each do |member|
.overflow-hidden.border.border-gray-100.shadow-sm
.h-0.overflow-hidden.relative{class: "pt-[125%]"}
=link_to member , class: "absolute h-60 top-0 left-0 w-full h-full" do
= picture_for( member , class: "object-cover")
%h3.pt-5.text-2xl.bg-gray-100.text-black.font-bold.text-center
= member.name
.p-2.text-xs.bg-gray-50.text-black.font-bold.text-center
= member.name
%div.h-full
.p-5.text-center
.m-2.text-sm.leading-relaxed.line-clamp-3{ prose_classes }
= markdown shorten(member.bio)

View File

@ -0,0 +1,57 @@
.px-4.py-16.mx-auto.sm:max-w-xl.md:max-w-full.lg:max-w-screen-xl.md:px-24.lg:px-8.lg:py-20
.flex.flex-col.max-w-screen-lg.overflow-hidden.bg-white.border.rounded.shadow-sm.lg:flex-row.sm:mx-auto
.relative{:class => "lg:w-1/2"}
-if @member.picture_url
= image_tag @member.picture_url, class: "object-cover w-full lg:absolute h-80 lg:h-full"
.flex.flex-col.justify-center.p-8.lg:p-16.lg:pl-10{:class => "lg:w-1/2"}
%div
%p.inline-block.px-3.py-px.mb-4.text-xs.font-semibold.tracking-wider.text-teal-900.uppercase.rounded-full.bg-teal-accent-400
= @member.name
%h5.mb-3.text-3xl.font-extrabold.leading-none.sm:text-4xl
= @member.name
.mb-8.text-gray-800
.prose= markdown(@member.bio)
- if current_member == @member
.flex.justify-around.ml-20
= link_to edit_member_path(@member) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit Profile
= link_to edit_member_registration_path do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Change Password
= link_to new_story_path() do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
New Story
= link_to new_picture_path() do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
New Picture
= form_tag( destroy_member_session_path , {method: :delete } ) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400{type: :submit}
Sign out
- @member.stories.each do |story|
=render_story( story )
- if current_member == @member
.flex.justify-around
= link_to edit_story_path(story) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit Story
.mx-20.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.xl:grid-cols-4.gap-8.md:gap-12.lg:gap-16
- @member.pictures.each do |picture|
%div
= render picture , picture: picture
- if current_member == @member
.flex.justify-around
= link_to edit_picture_path(picture) do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit Picture
:javascript
document.addEventListener("DOMContentLoaded", function(event) {
const lightbox = GLightbox({ });
});

View File

@ -0,0 +1,8 @@
.mx-20.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.2xl:grid-cols-4.gap-8.md:gap-12.lg:gap-16
- Picture.all.limit(4).each do |picture|
= render "pictures/picture" , picture: picture
:javascript
document.addEventListener("DOMContentLoaded", function(event) {
const lightbox = GLightbox({ });
});

View File

@ -0,0 +1,2 @@
- story = Story.last
= render_story story

View File

@ -0,0 +1,18 @@
%div
Pictures may have a small text, that will be diplayed on top of the
picure. The Picture box is landscape with ratio 3/4.
= simple_form_for @picture do |f|
= f.error_notification
.flex.h-16.mt-2
= image_tag(@picture.picture_url , class: "align-middle mr-2") if @picture.picture?
.w-full= f.input :picture , as: :file ,
label: (@picture.picture.blank? ? "Add picture" : "Change picture")
= f.hidden_field :picture_cache
= f.input :text , as: :text , input_html: { rows: 2 }
= f.input :happened , wrapper_class: "flex mt-4 align-center"
.flex.justify-between.mt-6
%button.bg-cyan-200.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
= f.submit 'Save'
= link_to member_path(current_member) do
%button.ml-20.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Back

View File

@ -0,0 +1,13 @@
.group.relative.overflow-hidden
.flex.justify-between
.ml-2= link_to picture.member.name , main_app.member_path(picture.member) , class: :underline
.mr-2
= distance_of_time_in_words_to_now picture.happened
ago
.h-0.overflow-hidden.relative{class: "pt-[75%]"}
=link_to someones_path( picture ) , {class: :glightbox ,
"data-glightbox" => "title: #{picture.text}" } do
= picture_for( picture , "absolute top-0 left-0 w-full h-full inset-0 object-cover hover:scale-105 ease-in duration-500")
-unless picture.text.blank?
.absolute.bottom-0.leaving-0.right-0.px-4.pb-1.bg-gray-800.opacity-70.transition-colors.group-hover:bg-black.group-hover:opacity-100
.text-center.mt-2.text-white= picture.text

View File

@ -0,0 +1,7 @@
%script{:src => "https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"}
%script{:src => "https://cdn.jsdelivr.net/npm/marked/marked.min.js"}
.flex.justify-center
.column{class: "w-10/12 md:w-8/12 lg:w-7/12 xl:w-6/12"}
.text-2xl.font-bold.my-4
Edit Picture
= render 'form'

View File

@ -0,0 +1,21 @@
= paginate @pictures
.flex.justify-end.mr-10
= sort_link(@q, :happened ,class: 'flex flex-nowrap text-md')
.border-r-4.mx-4
= sort_link(@q, :created_at , class: 'flex flex-nowrap text-md')
.mx-20.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.2xl:grid-cols-4.gap-8.md:gap-12.lg:gap-16
- @pictures.each do |picture|
= render picture , picture: picture
.flex.ml-20
= link_to new_picture_path do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
New Picture
:javascript
document.addEventListener("DOMContentLoaded", function(event) {
const lightbox = GLightbox({ });
});

View File

@ -0,0 +1,7 @@
%script{:src => "https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"}
%script{:src => "https://cdn.jsdelivr.net/npm/marked/marked.min.js"}
.flex.justify-center
.column{class: "w-10/12 md:w-8/12 lg:w-7/12 xl:w-6/12"}
.text-2xl.font-bold.my-4
New Picture
= render 'form'

View File

@ -0,0 +1,11 @@
.grid.grid-cols-1.md:grid-cols-2.lg:grid-cols-3.gap-8.md:gap-12.lg:gap-16
%div
= render @picture , picture: @picture
.mx-20.flex.justify-between
= link_to edit_picture_path(@picture) do
%button.mt-6.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit
= link_to pictures_path do
%button.mt-6.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Back

View File

@ -0,0 +1,20 @@
%div
Story layout changes with the amount of text.
For short text a wide picture is best. Otherwise square, and for
longer text a high picture also works.
= simple_form_for @story do |f|
= f.error_notification
.flex.h-16.mt-2
= image_tag(@story.picture_url , class: "align-middle mr-2") if @story.picture?
.w-full= f.input :picture , as: :file ,
label: (@story.picture.blank? ? "Add picture" : "Change picture")
= f.hidden_field :picture_cache
= f.input :header
= render "merged/form/editor" , object: @story , field: :text, form: f
= f.input :happened , wrapper_class: "flex mt-4 align-center"
.flex.justify-between.mt-6
%button.bg-cyan-200.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
= f.submit 'Save'
= link_to member_path(current_member) do
%button.ml-20.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Back

View File

@ -0,0 +1,13 @@
%section.overflow-hidden.grid.grid-cols-1.m-5.md:m-12.lg:m-20.md:grid-cols-2
=link_to someones_path( story ) , {class: :glightbox } do
= picture_for( story , "h-56 w-full object-cover sm:h-full")
.p-8.md:p-12.lg:px-16.lg:py-24
.mx-auto.max-w-xl.text-center
%h2.text-2xl.font-bold.md:text-4xl
= story.header
%h4.text-xl.mt-10.md:text-2xl
= distance_of_time_in_words_to_now story.happened
ago, by
= link_to story.member.name , main_app.member_path(story.member) , class: :underline
.mt-8{ prose_classes }
= markdown(story.text)

View File

@ -0,0 +1,15 @@
%section.flex.justify-center.p-8.flex-col.md:flex-row.m-20
.flex.items-center.h-40.md:h-60.lg:h-96.w-full.overflow-hidden{class: "lg:w-2/3"}
=link_to someones_path( story ) , {class: :glightbox } do
= picture_for(story ,"object-cover")
.flex.items-center.w-full.max-w.px-6.mt-6.mx-auto{:class => "lg:w-1/3"}
.flex-1
.text-center
%h2.text-4xl.font-bold.text-center.mb-4.lg:mb-8
= story.header
%h4.text-xl.mt-4.lg:mt-8.md:text-2xl
= distance_of_time_in_words_to_now story.happened
ago, by
= link_to story.member.name , main_app.member_path(story.member) , class: :underline
.mt-3{ prose_classes }
= markdown(story.text)

View File

@ -0,0 +1,13 @@
%section.p-20.my-20
.flex.justify-center
.max-w-prose.text-center
%h1.mb-10.text-2xl.font-bold.tracking-tight.sm:text-4xl
= story.header
.text-xl.pb-6{ prose_classes }
= distance_of_time_in_words_to_now story.happened
ago, by
= link_to story.member.name , main_app.member_path(story.member) , class: :underline
.max-w-full.mt-4.gap-16.columns-1.md:columns-2.lg:columns-3.xl:columns-4{ prose_classes }
=link_to someones_path( story ) , {class: :glightbox } do
= picture_for( story , "h-56 w-full object-cover sm:h-full")
= markdown(story.text)

View File

@ -0,0 +1,7 @@
%script{:src => "https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"}
%script{:src => "https://cdn.jsdelivr.net/npm/marked/marked.min.js"}
.flex.justify-center
.column{class: "w-10/12 md:w-8/12 lg:w-7/12 xl:w-6/12"}
.text-2xl.font-bold.my-4
Edit Story
= render 'form'

View File

@ -0,0 +1,21 @@
= paginate @stories
.flex.justify-end.mr-10
= sort_link(@q, :happened ,class: 'flex flex-nowrap text-md')
.border-r-4.mx-4
= sort_link(@q, :created_at , class: 'flex flex-nowrap text-md')
- @stories.each do |story|
= render_story(story)
%br
.flex.ml-20
= link_to new_story_path do
%button.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
New Story
:javascript
document.addEventListener("DOMContentLoaded", function(event) {
const lightbox = GLightbox({ });
});

View File

@ -0,0 +1,7 @@
%script{:src => "https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"}
%script{:src => "https://cdn.jsdelivr.net/npm/marked/marked.min.js"}
.flex.justify-center
.column{class: "w-10/12 md:w-8/12 lg:w-7/12 xl:w-6/12"}
.text-2xl.font-bold.my-4
New Story
= render 'form'

View File

@ -0,0 +1,9 @@
= render_story @story
.mx-20.flex.justify-between
= link_to edit_story_path(@story) do
%button.mt-6.bg-cyan-200.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Edit
= link_to stories_path do
%button.mt-6.mr-3.inline-block.rounded-lg.px-4.py-3.text-md.font-medium.border.border-gray-400
Back