devel #322

Merged
inhji merged 4 commits from devel into main 2023-09-13 21:00:45 +02:00
13 changed files with 358 additions and 84 deletions

View file

@ -2,9 +2,9 @@
@import "tailwindcss/components"; @import "tailwindcss/components";
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
@import "./hljs.css";
@import "./lightbox.css"; @import "./lightbox.css";
@import "./tablesort.css"; @import "./tablesort.css";
@import "./tokyo-night-dark.css";
/* /*
The base layer is for things like reset rules or default styles applied to plain HTML elements. The base layer is for things like reset rules or default styles applied to plain HTML elements.
@ -33,6 +33,10 @@
margin-block-start: var(--flow-space, 1em); margin-block-start: var(--flow-space, 1em);
} }
:root .prose {
@apply prose-inhji;
}
:root[data-mode=dark] .prose { :root[data-mode=dark] .prose {
@apply prose-invert; @apply prose-invert;
} }
@ -185,6 +189,29 @@
@apply hidden; @apply hidden;
} }
} }
/* === CARD LIST (ADMIN) === */
.card-list {
@apply flex flex-col gap-3 mt-6;
}
/* === CARD (ADMIN) === */
.card {
@apply bg-neutral-100 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 p-3 rounded;
& header {
& h2 {
@apply text-xl leading-normal;
}
}
& footer {
@apply flex gap-3 text-sm;
}
}
} }
/* /*

201
assets/css/hljs.css Normal file
View file

@ -0,0 +1,201 @@
:root {
/*!
Theme: a11y-light
Author: @ericwbailey
Maintainer: @ericwbailey
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
*/
& .hljs {
background: #fefefe;
color: #545454;
}
/* Comment */
& .hljs-comment,
& .hljs-quote {
color: #696969;
}
/* Red */
& .hljs-variable,
& .hljs-template-variable,
& .hljs-tag,
& .hljs-name,
& .hljs-selector-id,
& .hljs-selector-class,
& .hljs-regexp,
& .hljs-deletion {
color: #d91e18;
}
/* Orange */
& .hljs-number,
& .hljs-built_in,
& .hljs-literal,
& .hljs-type,
& .hljs-params,
& .hljs-meta,
& .hljs-link {
color: #aa5d00;
}
/* Yellow */
& .hljs-attribute {
color: #aa5d00;
}
/* Green */
& .hljs-string,
& .hljs-symbol,
& .hljs-bullet,
& .hljs-addition {
color: #008000;
}
/* Blue */
& .hljs-title,
& .hljs-section {
color: #007faa;
}
/* Purple */
& .hljs-keyword,
& .hljs-selector-tag {
color: #7928a1;
}
& .hljs-emphasis {
font-style: italic;
}
& .hljs-strong {
font-weight: bold;
}
@media screen and (-ms-high-contrast: active) {
& .hljs-addition,
& .hljs-attribute,
& .hljs-built_in,
& .hljs-bullet,
& .hljs-comment,
& .hljs-link,
& .hljs-literal,
& .hljs-meta,
& .hljs-number,
& .hljs-params,
& .hljs-string,
& .hljs-symbol,
& .hljs-type,
& .hljs-quote {
color: highlight;
}
& .hljs-keyword,
& .hljs-selector-tag {
font-weight: bold;
}
}
}
:root[data-mode=dark] {
/*!
Theme: a11y-dark
Author: @ericwbailey
Maintainer: @ericwbailey
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
*/
& .hljs {
background: #2b2b2b;
color: #f8f8f2;
}
/* Comment */
& .hljs-comment,
& .hljs-quote {
color: #d4d0ab;
}
/* Red */
& .hljs-variable,
& .hljs-template-variable,
& .hljs-tag,
& .hljs-name,
& .hljs-selector-id,
& .hljs-selector-class,
& .hljs-regexp,
& .hljs-deletion {
color: #ffa07a;
}
/* Orange */
& .hljs-number,
& .hljs-built_in,
& .hljs-literal,
& .hljs-type,
& .hljs-params,
& .hljs-meta,
& .hljs-link {
color: #f5ab35;
}
/* Yellow */
& .hljs-attribute {
color: #ffd700;
}
/* Green */
& .hljs-string,
& .hljs-symbol,
& .hljs-bullet,
& .hljs-addition {
color: #abe338;
}
/* Blue */
& .hljs-title,
& .hljs-section {
color: #00e0e0;
}
/* Purple */
& .hljs-keyword,
& .hljs-selector-tag {
color: #dcc6e0;
}
& .hljs-emphasis {
font-style: italic;
}
& .hljs-strong {
font-weight: bold;
}
@media screen and (-ms-high-contrast: active) {
& .hljs-addition,
& .hljs-attribute,
& .hljs-built_in,
& .hljs-bullet,
& .hljs-comment,
& .hljs-link,
& .hljs-literal,
& .hljs-meta,
& .hljs-number,
& .hljs-params,
& .hljs-string,
& .hljs-symbol,
& .hljs-type,
& .hljs-quote {
color: highlight;
}
& .hljs-keyword,
& .hljs-selector-tag {
font-weight: bold;
}
}
}

View file

@ -23,34 +23,34 @@ module.exports = {
background: 'rgb(var(--color-background) / <alpha-value>)' background: 'rgb(var(--color-background) / <alpha-value>)'
}, },
typography: ({ theme }) => ({ typography: ({ theme }) => ({
colorful: { inhji: {
css: { css: {
'--tw-prose-lead': theme('colors.rose[700]'), '--tw-prose-lead': theme('colors.neutral[700]'),
'--tw-prose-links': theme('colors.rose[900]'), '--tw-prose-links': theme('colors.neutral[900]'),
'--tw-prose-counters': theme('colors.rose[600]'), '--tw-prose-counters': theme('colors.neutral[600]'),
'--tw-prose-bullets': theme('colors.rose[400]'), '--tw-prose-bullets': theme('colors.neutral[400]'),
'--tw-prose-hr': theme('colors.rose[300]'), '--tw-prose-hr': theme('colors.neutral[300]'),
'--tw-prose-quotes': theme('colors.rose[900]'), '--tw-prose-quotes': theme('colors.neutral[900]'),
'--tw-prose-quote-borders': theme('colors.rose[300]'), '--tw-prose-quote-borders': theme('colors.neutral[300]'),
'--tw-prose-captions': theme('colors.rose[700]'), '--tw-prose-captions': theme('colors.neutral[700]'),
'--tw-prose-code': theme('colors.rose[900]'), '--tw-prose-code': theme('colors.neutral[900]'),
'--tw-prose-pre-code': theme('colors.rose[100]'), '--tw-prose-pre-code': theme('colors.neutral[100]'),
'--tw-prose-pre-bg': theme('colors.rose[900]'), '--tw-prose-pre-bg': theme('colors.neutral[200]'),
'--tw-prose-th-borders': theme('colors.rose[300]'), '--tw-prose-th-borders': theme('colors.neutral[300]'),
'--tw-prose-td-borders': theme('colors.rose[200]'), '--tw-prose-td-borders': theme('colors.neutral[200]'),
'--tw-prose-invert-lead': theme('colors.rose[300]'), '--tw-prose-invert-lead': theme('colors.neutral[300]'),
'--tw-prose-invert-links': theme('colors.white'), '--tw-prose-invert-links': theme('colors.white'),
'--tw-prose-invert-counters': theme('colors.rose[400]'), '--tw-prose-invert-counters': theme('colors.neutral[400]'),
'--tw-prose-invert-bullets': theme('colors.rose[600]'), '--tw-prose-invert-bullets': theme('colors.neutral[600]'),
'--tw-prose-invert-hr': theme('colors.rose[700]'), '--tw-prose-invert-hr': theme('colors.neutral[700]'),
'--tw-prose-invert-quotes': theme('colors.rose[100]'), '--tw-prose-invert-quotes': theme('colors.neutral[100]'),
'--tw-prose-invert-quote-borders': theme('colors.rose[700]'), '--tw-prose-invert-quote-borders': theme('colors.neutral[700]'),
'--tw-prose-invert-captions': theme('colors.rose[400]'), '--tw-prose-invert-captions': theme('colors.neutral[400]'),
'--tw-prose-invert-code': theme('colors.white'), '--tw-prose-invert-code': theme('colors.white'),
'--tw-prose-invert-pre-code': theme('colors.rose[300]'), '--tw-prose-invert-pre-code': theme('colors.neutral[300]'),
'--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)', '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
'--tw-prose-invert-th-borders': theme('colors.rose[600]'), '--tw-prose-invert-th-borders': theme('colors.neutral[600]'),
'--tw-prose-invert-td-borders': theme('colors.rose[700]'), '--tw-prose-invert-td-borders': theme('colors.neutral[700]'),
}, },
}, },
}), }),

View file

@ -58,9 +58,11 @@ defmodule Chiya.Notes do
Chiya.Flop.validate_and_run(q, params, for: Chiya.Notes.Note) Chiya.Flop.validate_and_run(q, params, for: Chiya.Notes.Note)
end end
def list_apply_notes(regex) do def list_apply_notes(%Chiya.Tags.Tag{} = tag) do
Note Note
|> where([n], fragment("? ~ ?", n.name, ^regex)) |> where([n], fragment("? ~ ?", n.name, ^tag.regex))
|> or_where([n], fragment("? ~ ?", n.url, ^tag.regex))
|> or_where([n], fragment("? ~ ?", n.content, ^tag.regex))
|> Repo.all() |> Repo.all()
end end

View file

@ -15,5 +15,6 @@ defmodule Chiya.Notes.NoteTag do
note_tag note_tag
|> cast(attrs, [:note_id, :tag_id]) |> cast(attrs, [:note_id, :tag_id])
|> validate_required([:note_id, :tag_id]) |> validate_required([:note_id, :tag_id])
|> unique_constraint([:note_id, :tag_id])
end end
end end

View file

@ -38,9 +38,10 @@ defmodule Chiya.Tags do
|> Repo.all() |> Repo.all()
end end
def list_admin_tags(params) do def list_admin_tags() do
q = q =
Tag Tag
|> order_by(:name)
|> with_preloads() |> with_preloads()
Repo.all(q) Repo.all(q)

View file

@ -8,6 +8,13 @@ defmodule Chiya.Tags.TagUpdater do
alias Chiya.{Notes, Tags} alias Chiya.{Notes, Tags}
alias Chiya.Notes.Note alias Chiya.Notes.Note
@doc """
Updates a tag for the given note.
## Examples
iex> update_tags({:ok, note}, "foo,bar")
"""
def update_tags({:ok, %Note{} = note}, attrs) do def update_tags({:ok, %Note{} = note}, attrs) do
note note
|> Notes.preload_note() |> Notes.preload_note()
@ -20,27 +27,19 @@ defmodule Chiya.Tags.TagUpdater do
{:error, changeset} {:error, changeset}
end end
@doc """ def update_tags(%Note{} = note, %{tags_string: new_tags} = attrs) when is_map(attrs) do
Updates the tags for the given note
## Examples
iex> update_tags(note, "foo,bar")
"""
def update_tags(note, %{tags_string: new_tags} = attrs) when is_map(attrs) do
update_tags(note, new_tags) update_tags(note, new_tags)
end end
def update_tags(note, %{"tags_string" => new_tags} = attrs) when is_map(attrs) do def update_tags(%Note{} = note, %{"tags_string" => new_tags} = attrs) when is_map(attrs) do
update_tags(note, new_tags) update_tags(note, new_tags)
end end
def update_tags(note, attrs) when is_map(attrs) do def update_tags(%Note{} = note, attrs) when is_map(attrs) do
note note
end end
def update_tags(note, new_tags) when is_binary(new_tags) do def update_tags(%Note{} = note, new_tags) when is_binary(new_tags) do
update_tags(note, split_tags(new_tags)) update_tags(note, split_tags(new_tags))
end end
@ -62,14 +61,7 @@ defmodule Chiya.Tags.TagUpdater do
|> remove_tags(old_tags -- new_tags) |> remove_tags(old_tags -- new_tags)
end end
defp split_tags(tags_string) when is_binary(tags_string) do def add_tags(note, tags) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
defp add_tags(note, tags) do
tags tags
|> Enum.uniq() |> Enum.uniq()
|> Enum.each(&add_tag(note, &1)) |> Enum.each(&add_tag(note, &1))
@ -77,6 +69,13 @@ defmodule Chiya.Tags.TagUpdater do
note note
end end
defp split_tags(tags_string) when is_binary(tags_string) do
tags_string
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.filter(&(String.length(&1) > 0))
end
defp add_tag(%{id: note_id} = note, tag) when is_binary(tag) do defp add_tag(%{id: note_id} = note, tag) when is_binary(tag) do
slug = Slugger.slugify_downcase(tag) slug = Slugger.slugify_downcase(tag)
@ -100,7 +99,13 @@ defmodule Chiya.Tags.TagUpdater do
tag_id: tag.id tag_id: tag.id
} }
{:ok, _note_tag} = Notes.create_note_tag(attrs) case Notes.create_note_tag(attrs) do
{:ok, _note_tag} ->
true
{:error, changeset} ->
Logger.warn(inspect(changeset))
end
end end
end end

View file

@ -8,21 +8,15 @@ defmodule ChiyaWeb.PageController do
defp put_assigns(conn, opts) do defp put_assigns(conn, opts) do
conn conn
|> assign(:page_header, true) |> assign(:page_header, true)
|> assign(:page_title, "no title")
end end
def home(conn, params) do def home(conn, params) do
settings = conn.assigns.settings settings = conn.assigns.settings
{channel, notes, meta} = if settings.home_channel_id do
case settings.home_channel_id do channel = Channels.get_channel!(settings.home_channel_id)
nil ->
nil
id ->
channel = Channels.get_channel!(id)
{:ok, {notes, meta}} = Chiya.Notes.list_home_notes(channel, params) {:ok, {notes, meta}} = Chiya.Notes.list_home_notes(channel, params)
{channel, notes, meta}
end
render(conn, :home, render(conn, :home,
channel: channel, channel: channel,
@ -31,6 +25,9 @@ defmodule ChiyaWeb.PageController do
page_title: "Home", page_title: "Home",
page_header: false page_header: false
) )
else
render_error(conn, :not_found)
end
end end
def channel(conn, %{"slug" => channel_slug}) do def channel(conn, %{"slug" => channel_slug}) do

View file

@ -1,6 +1,6 @@
defmodule ChiyaWeb.PageHTML do defmodule ChiyaWeb.PageHTML do
use ChiyaWeb, :html_public use ChiyaWeb, :html_public
import ChiyaWeb.Format, only: [pretty_datetime: 1, pretty_date: 1, datetime: 1] import ChiyaWeb.Format, only: [pretty_datetime: 1, pretty_date: 1]
embed_templates "page_html/*" embed_templates "page_html/*"

View file

@ -2,8 +2,8 @@ defmodule ChiyaWeb.TagController do
use ChiyaWeb, :controller use ChiyaWeb, :controller
alias Chiya.Tags alias Chiya.Tags
def index(conn, params) do def index(conn, _params) do
tags = Chiya.Tags.list_admin_tags(params) tags = Chiya.Tags.list_admin_tags()
render(conn, :index, tags: tags) render(conn, :index, tags: tags)
end end
@ -13,13 +13,26 @@ defmodule ChiyaWeb.TagController do
render(conn, :show, tag: tag) render(conn, :show, tag: tag)
end end
def apply(conn, %{"id" => id}) do def apply_prepare(conn, %{"id" => id}) do
tag = Tags.get_tag!(id) tag = Tags.get_tag!(id)
notes = Chiya.Notes.list_apply_notes(tag.regex) notes = Chiya.Notes.list_apply_notes(tag)
render(conn, :apply_prepare, tag: tag, notes: notes) render(conn, :apply_prepare, tag: tag, notes: notes)
end end
def apply_run(conn, %{"id" => id}) do
tag = Tags.get_tag!(id)
notes = Chiya.Notes.list_apply_notes(tag)
Enum.each(notes, fn note ->
IO.inspect("Updating note: #{note.name}")
Chiya.Tags.TagUpdater.add_tags(note, [tag.slug])
end)
notes = Chiya.Notes.list_apply_notes(tag)
render(conn, :apply_prepare, tag: tag, notes: notes)
end
def edit(conn, %{"id" => id}) do def edit(conn, %{"id" => id}) do
IO.inspect(id) IO.inspect(id)
tag = Tags.get_tag!(id) tag = Tags.get_tag!(id)

View file

@ -1,22 +1,22 @@
<.header> <.header>
Apply Tag <%= @tag.name %> Apply Tag <%= @tag.name %>
<:subtitle>These notes will get the tag.</:subtitle> <:subtitle><%= Enum.count(@notes) %> notes will get the tag.</:subtitle>
<:actions> <:actions>
<.link href={~p"/admin/tags/#{@tag}/apply"}> <.link href={~p"/admin/tags/#{@tag}/apply/run"}>
<.button>Apply tag</.button> <.button>Apply tag</.button>
</.link> </.link>
</:actions> </:actions>
</.header> </.header>
<section> <section class="card-list">
<%= for note <- @notes do %> <%= for note <- @notes do %>
<article class="bg-slate-100 dark:bg-slate-800 text-slate-900 dark:text-slate-100 p-3 rounded"> <article class="card">
<header> <header>
<h2 class="text-xl leading-normal"> <h2>
<a href={"/admin/notes/#{note.id}"}><%= note.name %></a> <a href={"/admin/notes/#{note.id}"}><%= note.name %></a>
</h2> </h2>
</header> </header>
<footer class="flex gap-3 text-sm "> <footer>
<span>Updated <%= from_now(note.updated_at) %></span> <span>Updated <%= from_now(note.updated_at) %></span>
<span>Published <%= from_now(note.published_at) %></span> <span>Published <%= from_now(note.published_at) %></span>
</footer> </footer>

View file

@ -3,17 +3,43 @@
<:subtitle>Tags are tags.</:subtitle> <:subtitle>Tags are tags.</:subtitle>
</.header> </.header>
<section class="flex flex-col gap-3 mt-6"> <section class="card-list mt-8">
<%= for tag <- @tags do %> <h3 class="leading-normal text-xl">With Regex</h3>
<article class="bg-neutral-100 dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 p-3 rounded">
<%= for tag <- Enum.filter(@tags, fn t -> !!t.regex end) do %>
<article class="card">
<header> <header>
<h2 class="text-xl leading-normal"> <h2>
<a href={"/admin/tags/#{tag.id}"}><%= tag.name %></a> <a href={"/admin/tags/#{tag.id}"}><%= tag.name %></a>
</h2> </h2>
</header> </header>
<footer class="flex gap-3 text-sm "> <footer>
<span><%= Enum.count(tag.notes) %> notes</span> <span><%= Enum.count(tag.notes) %> notes</span>
<span>Updated <%= from_now(tag.updated_at) %></span> <span>Updated <%= from_now(tag.updated_at) %></span>
<%= if String.length(tag.regex || "") > 0 do %>
<span>Regex: <%= tag.regex %></span>
<% end %>
</footer>
</article>
<% end %>
</section>
<section class="card-list mt-8">
<h3 class="leading-normal text-xl">Without Regex</h3>
<%= for tag <- Enum.filter(@tags, fn t -> !t.regex end) do %>
<article class="card">
<header>
<h2>
<a href={"/admin/tags/#{tag.id}"}><%= tag.name %></a>
</h2>
</header>
<footer>
<span><%= Enum.count(tag.notes) %> notes</span>
<span>Updated <%= from_now(tag.updated_at) %></span>
<%= if String.length(tag.regex || "") > 0 do %>
<span>Regex: <%= tag.regex %></span>
<% end %>
</footer> </footer>
</article> </article>
<% end %> <% end %>

View file

@ -77,7 +77,8 @@ defmodule ChiyaWeb.Router do
resources "/tokens", TokenController, only: [:index, :show, :new, :create, :delete] resources "/tokens", TokenController, only: [:index, :show, :new, :create, :delete]
resources "/tags", TagController, only: [:index, :edit, :update, :show] resources "/tags", TagController, only: [:index, :edit, :update, :show]
get "/tags/:id/apply", TagController, :apply get "/tags/:id/apply", TagController, :apply_prepare
get "/tags/:id/apply/run", TagController, :apply_run
get "/notes/import", NoteController, :import_prepare get "/notes/import", NoteController, :import_prepare
post "/notes/import", NoteController, :import_run post "/notes/import", NoteController, :import_run