From: Aidan Cornelius-Bell Date: Sat, 14 Sep 2024 22:18:23 +0000 (+0930) Subject: stylesheet tweaks; generators with templates X-Git-Url: https://gitweb.mndrdr.org/?a=commitdiff_plain;h=de4ac4857b82290983293c99f1c7866659a2ccc8;p=arelpe.git stylesheet tweaks; generators with templates --- diff --git a/.DS_Store b/.DS_Store index 285cf43..92ecf43 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Gemfile b/Gemfile index 51d9fad..895f6f5 100644 --- a/Gemfile +++ b/Gemfile @@ -16,6 +16,8 @@ gem "kaminari" gem "redcarpet" #syntax gem "rouge" +#html crawling +gem "httparty" # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] # gem "kredis" diff --git a/Gemfile.lock b/Gemfile.lock index a2e4ba8..bc77713 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -95,6 +95,7 @@ GEM concurrent-ruby (1.3.4) connection_pool (2.4.1) crass (1.0.6) + csv (3.3.0) date (3.3.4) debug (1.9.2) irb (~> 1.10) @@ -109,6 +110,10 @@ GEM erubi (1.13.0) globalid (1.2.1) activesupport (>= 6.1) + httparty (0.22.0) + csv + mini_mime (>= 1.0.0) + multi_xml (>= 0.5.2) i18n (1.14.5) concurrent-ruby (~> 1.0) io-console (0.7.2) @@ -143,6 +148,8 @@ GEM mini_mime (1.1.5) minitest (5.25.1) msgpack (1.7.2) + multi_xml (0.7.1) + bigdecimal (~> 3.1) mysql2 (0.5.6) net-imap (0.4.16) date @@ -308,6 +315,7 @@ DEPENDENCIES capybara debug devise + httparty kaminari mysql2 (~> 0.5) puma (>= 5.0) diff --git a/app/.DS_Store b/app/.DS_Store index 3ced85b..3031a20 100644 Binary files a/app/.DS_Store and b/app/.DS_Store differ diff --git a/app/views/.DS_Store b/app/assets/.DS_Store similarity index 97% copy from app/views/.DS_Store copy to app/assets/.DS_Store index 21c52c5..893ce86 100644 Binary files a/app/views/.DS_Store and b/app/assets/.DS_Store differ diff --git a/app/controllers/concerns/.DS_Store b/app/assets/images/.DS_Store similarity index 100% copy from app/controllers/concerns/.DS_Store copy to app/assets/images/.DS_Store diff --git a/app/assets/images/logo-192.png b/app/assets/images/logo-192.png index d6baf79..a28fdb9 100644 Binary files a/app/assets/images/logo-192.png and b/app/assets/images/logo-192.png differ diff --git a/app/assets/images/porter.svg b/app/assets/images/porter.svg new file mode 100644 index 0000000..57e7c32 --- /dev/null +++ b/app/assets/images/porter.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 980697d..f4e73da 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -183,7 +183,6 @@ select { font-size: 16px; } -/* Style for focus state */ input[type="text"]:focus, input[type="email"]:focus, input[type="password"]:focus, @@ -194,7 +193,6 @@ select:focus { outline: none; } -/* Style for submit button */ input[type="submit"] { padding: 14px 20px; margin: 8px 0; @@ -204,13 +202,16 @@ input[type="submit"] { font-size: 16px; } -/* Style for labels */ label { font-weight: bold; margin-bottom: 5px; display: block; } +.subheading { + margin-top: -25px; +} + .post { background-color: var(--post-bg); padding: 2em; @@ -246,6 +247,12 @@ footer p { font-size: .8rem; } +.porter { + max-width: 170px; + float: right; + padding: 20px; +} + .logo { max-width: 48px; float: right; @@ -300,6 +307,19 @@ ul .pinned::before { white-space: nowrap; } +.notice { + background: var(--post-bg); + border-radius: 4px; + padding: 1px 1.5em; + margin: 10px auto; + max-width: 290px; + color: var(--accent-c); +} + +.notice h3 { + color: var(--accent-a); +} + .filter-buttons { margin-bottom: 20px; text-align: center; @@ -334,7 +354,7 @@ ul .pinned::before { .bookmark-comment { font-style: italic; - color: var(--code-text); + color: var(--accent-b); font-size: 0.9em; } diff --git a/app/controllers/api_keys_controller.rb b/app/controllers/api_keys_controller.rb index d1898f7..012a0f5 100644 --- a/app/controllers/api_keys_controller.rb +++ b/app/controllers/api_keys_controller.rb @@ -1,7 +1,7 @@ class ApiKeysController < ApplicationController before_action :set_api_key, only: [:show, :destroy] - before_action :authenticate_user! # Assuming you're using Devise for authentication - before_action :authorize_admin # You'll need to implement this method + #before_action :authenticate_user! + include AdminAuthenticatable def index @api_keys = ApiKey.all diff --git a/app/controllers/concerns/admin_authenticatable.rb b/app/controllers/concerns/admin_authenticatable.rb index ebe9da5..2397948 100644 --- a/app/controllers/concerns/admin_authenticatable.rb +++ b/app/controllers/concerns/admin_authenticatable.rb @@ -9,7 +9,7 @@ module AdminAuthenticatable def authenticate_admin! unless current_user&.admin? - flash[:alert] = "You are not authorized to access this page." + flash[:alert] = "You are not authorised to access this page. If you have an account please log in first." redirect_to root_path end end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index d8e7f03..4b39f11 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -46,6 +46,47 @@ class PostsController < ApplicationController @post.destroy! redirect_to posts_url, notice: "Post was successfully destroyed.", status: :see_other end + + def export + @posts = Post.order(published_at: :desc) + send_data @posts.to_json, filename: "posts_export.json", type: "application/json" + end + + def importer + end + + def import + if params[:file].present? + file = params[:file] + if file.content_type == "application/json" + begin + data = JSON.parse(file.read) + import_results = { success: 0, failed: 0, errors: [] } + + ActiveRecord::Base.transaction do + import_posts(data['posts'], import_results) if data['posts'] + import_bookmarks(data['bookmarks'], import_results) if data['bookmarks'] + end + + if import_results[:failed] > 0 + flash[:warning] = "Import partially succeeded. Successful: #{import_results[:success]}, Failed: #{import_results[:failed]}. Check the logs for detailed errors." + else + flash[:notice] = "Data imported successfully. Total items: #{import_results[:success]}" + end + rescue JSON::ParserError + flash[:alert] = "Invalid JSON file." + rescue => e + flash[:alert] = "Error importing data: #{e.message}" + logger.error "Import Error: #{e.message}\n#{e.backtrace.join("\n")}" + end + else + flash[:alert] = "Please upload a JSON file." + end + else + flash[:alert] = "Please select a file to import." + end + redirect_to posts_path + end private # Use callbacks to share common setup or constraints between actions. @@ -55,6 +96,70 @@ class PostsController < ApplicationController # Only allow a list of trusted parameters through. def post_params - params.require(:post).permit(:post_type, :title, :slug, :published_at, :excerpt, :tags, :content, :url) + params.require(:post).permit(:post_type, :title, :slug, :published_at, :excerpt, :tags, :content, :url, :file) + end + + def import_posts(posts_data, results) + return unless posts_data.is_a?(Array) + + posts_data.each do |post_data| + begin + post = Post.new( + post_type: 'dispatch', + title: post_data['title'], + slug: post_data['slug'], + published_at: post_data['date'], + content: post_data['content'], + tags: post_data['tags'] + ) + + if post.save + results[:success] += 1 + else + results[:failed] += 1 + results[:errors] << "Post '#{post_data['title']}': #{post.errors.full_messages.join(', ')}" + end + rescue => e + results[:failed] += 1 + results[:errors] << "Post '#{post_data['title']}': #{e.message}" + logger.error "Error importing post: #{post_data.inspect}\nError: #{e.message}\n#{e.backtrace.join("\n")}" + end + end + end + + def import_bookmarks(bookmarks_data, results) + return unless bookmarks_data.is_a?(Array) + + bookmarks_data.each do |bookmark_data| + begin + comment = bookmark_data['comment'] + title, content = if comment.include?('«') + comment.split('«', 2).map(&:strip) + else + [comment, nil] + end + + post = Post.new( + post_type: 'bookmark', + title: title, + published_at: bookmark_data['datetime'], + excerpt: title, + content: content, + tags: bookmark_data['starred'] == 1 ? 'starred' : '', + url: bookmark_data['url'] + ) + + if post.save + results[:success] += 1 + else + results[:failed] += 1 + results[:errors] << "Bookmark '#{bookmark_data['url']}': #{post.errors.full_messages.join(', ')}" + end + rescue => e + results[:failed] += 1 + results[:errors] << "Bookmark '#{bookmark_data['url']}': #{e.message}" + logger.error "Error importing bookmark: #{bookmark_data.inspect}\nError: #{e.message}\n#{e.backtrace.join("\n")}" + end + end end end diff --git a/app/models/concerns/url_title_fetchable.rb b/app/models/concerns/url_title_fetchable.rb new file mode 100644 index 0000000..efa6338 --- /dev/null +++ b/app/models/concerns/url_title_fetchable.rb @@ -0,0 +1,38 @@ +require 'open-uri' + +module UrlTitleFetchable + extend ActiveSupport::Concern + + def fetch_title_from_url(url) + options = { + 'User-Agent' => 'facebookexternalhit/1.0', + ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, + read_timeout: 12 + } + + begin + doc = Nokogiri::HTML(URI.open(url, options)) + title = doc.at_css('title')&.text&.strip + clean_title(title) if title.present? + rescue StandardError => e + Rails.logger.error("Error fetching title for URL #{url}: #{e.message}") + nil + end + end + + private + + def clean_title(title) + # List of common separators + separators = %w[| - : • · — – →] + + # Create a regex pattern from the separators + separator_pattern = Regexp.union(separators) + + # Split the title at the first occurrence of any separator + cleaned_title = title.split(separator_pattern).first&.strip + + # Return the cleaned title if it's not empty, otherwise return the original title + cleaned_title.present? ? cleaned_title : title + end +end \ No newline at end of file diff --git a/app/models/post.rb b/app/models/post.rb index 4cc2ea7..64e613e 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,12 +1,15 @@ class Post < ApplicationRecord + include UrlTitleFetchable + validates :post_type, presence: true, inclusion: { in: %w[dispatch bookmark] } validates :title, presence: true - validates :slug, presence: true, uniqueness: true + validates :slug, presence: true, uniqueness: true, if: :dispatch? validates :published_at, presence: true, if: :dispatch? validates :content, presence: true, if: :dispatch? validates :url, presence: true, if: :bookmark? before_validation :set_slug, if: :new_record? + before_validation :set_title_from_url, if: -> { bookmark? && title.blank? } scope :dispatches, -> { where(post_type: 'dispatch') } scope :bookmarks, -> { where(post_type: 'bookmark') } @@ -59,4 +62,21 @@ class Post < ApplicationRecord def set_published_at self.published_at ||= Time.current end + + private + + def set_title_from_url + return unless bookmark? && url.present? && title.blank? + + Rails.logger.debug("Attempting to fetch title for URL: #{url}") + fetched_title = fetch_title_from_url(url) + + if fetched_title.present? + Rails.logger.debug("Successfully fetched title: #{fetched_title}") + self.title = fetched_title + else + Rails.logger.debug("Failed to fetch title, using default") + self.title = "Bookmark" + end + end end \ No newline at end of file diff --git a/app/views/posts/importer.html.erb b/app/views/posts/importer.html.erb new file mode 100644 index 0000000..3a91bc6 --- /dev/null +++ b/app/views/posts/importer.html.erb @@ -0,0 +1,26 @@ +
+

Data importer

+ <%= link_to "Back to posts", posts_path, class: "button" %> +
+
+
+ <%= image_tag "porter.svg", class: "porter" %> + + <%= form_tag import_path, multipart: true do %> +
+ <%= label_tag :file, "Select JSON file to import" %> + <%= file_field_tag :file, accept: 'application/json' %> +
+ <%= submit_tag "Import Data", class: "button" %> + <% end %> + +

What are we doing here?

+

“Give me your tired, your poor,
+ Your huddled masses yearning to breathe free,
+ The wretched refuse of your teeming shore.
+ Send these, the homeless, tempest-tost to me,
+ I lift my lamp beside the golden door!”

+ — Emma Lazarus + +
+
\ No newline at end of file diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb index 748e7a6..70da4e4 100644 --- a/app/views/posts/index.html.erb +++ b/app/views/posts/index.html.erb @@ -2,6 +2,8 @@ <% content_for :title, "Posts" %>

Posts

<%= link_to "New post", new_post_path, class: "button" %> <%= link_to "Home", root_path, class: "button" %> + <%= link_to "Export Data", export_path, class: "button" %> + <%= link_to "Import Data", importer_path, class: "button" %>
diff --git a/app/views/posts/show.html.erb b/app/views/posts/show.html.erb index cc3e9f3..d016f9d 100644 --- a/app/views/posts/show.html.erb +++ b/app/views/posts/show.html.erb @@ -10,7 +10,7 @@ <%= render @post %>
- <%= link_to "Edit this post", edit_post_path(@post) %> + <%= link_to "Edit this post", edit_post_path(@post), class: "button" %> <%= button_to "Destroy this post", @post, method: :delete %>
diff --git a/app/views/pubview/index.html.erb b/app/views/pubview/index.html.erb index 0ed4eb0..f78ca14 100644 --- a/app/views/pubview/index.html.erb +++ b/app/views/pubview/index.html.erb @@ -1,8 +1,15 @@
+<% if notice or alert %> +
+

Notice:

+

<%= notice or alert %>

+
+<% end %> <%= link_to root_path do %> <%= image_tag "logo-192.png", class: "logo" %> <% end %> -

Arelpe

+

mind reader

+

Aidan’s anti-capitalist posting and sharing project

@@ -30,7 +37,7 @@ <% else %> <%= link_to item.title, item.url, target: "_blank" %> - ↗︎ + ↗︎ « <%= item.content %> added <%= item.created_at.strftime('%l:%M%P on %d/%m/%y') if item.created_at.present? %> diff --git a/app/views/pubview/post.html.erb b/app/views/pubview/post.html.erb index ad8cab4..d5d8cee 100644 --- a/app/views/pubview/post.html.erb +++ b/app/views/pubview/post.html.erb @@ -1,4 +1,7 @@
+ <%= link_to root_path do %> + <%= image_tag "logo-192.png", class: "logo" %> + <% end %>

<%= @post.title %>

<%= link_to "↼ Back to some other ideas...", root_path %>