diff --git a/assets/css/app.css b/assets/css/app.css index 963c68c..afeef84 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -2,9 +2,9 @@ @import "tailwindcss/components"; @import "tailwindcss/utilities"; +@import "./hljs.css"; @import "./lightbox.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. @@ -33,6 +33,10 @@ margin-block-start: var(--flow-space, 1em); } + :root .prose { + @apply prose-inhji; + } + :root[data-mode=dark] .prose { @apply prose-invert; } @@ -185,6 +189,29 @@ @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; + } + } + + } /* diff --git a/assets/css/hljs.css b/assets/css/hljs.css new file mode 100644 index 0000000..ae3e51b --- /dev/null +++ b/assets/css/hljs.css @@ -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; + } + } +} \ No newline at end of file diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 5636983..cc594fd 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -23,34 +23,34 @@ module.exports = { background: 'rgb(var(--color-background) / )' }, typography: ({ theme }) => ({ - colorful: { + inhji: { css: { - '--tw-prose-lead': theme('colors.rose[700]'), - '--tw-prose-links': theme('colors.rose[900]'), - '--tw-prose-counters': theme('colors.rose[600]'), - '--tw-prose-bullets': theme('colors.rose[400]'), - '--tw-prose-hr': theme('colors.rose[300]'), - '--tw-prose-quotes': theme('colors.rose[900]'), - '--tw-prose-quote-borders': theme('colors.rose[300]'), - '--tw-prose-captions': theme('colors.rose[700]'), - '--tw-prose-code': theme('colors.rose[900]'), - '--tw-prose-pre-code': theme('colors.rose[100]'), - '--tw-prose-pre-bg': theme('colors.rose[900]'), - '--tw-prose-th-borders': theme('colors.rose[300]'), - '--tw-prose-td-borders': theme('colors.rose[200]'), - '--tw-prose-invert-lead': theme('colors.rose[300]'), + '--tw-prose-lead': theme('colors.neutral[700]'), + '--tw-prose-links': theme('colors.neutral[900]'), + '--tw-prose-counters': theme('colors.neutral[600]'), + '--tw-prose-bullets': theme('colors.neutral[400]'), + '--tw-prose-hr': theme('colors.neutral[300]'), + '--tw-prose-quotes': theme('colors.neutral[900]'), + '--tw-prose-quote-borders': theme('colors.neutral[300]'), + '--tw-prose-captions': theme('colors.neutral[700]'), + '--tw-prose-code': theme('colors.neutral[900]'), + '--tw-prose-pre-code': theme('colors.neutral[100]'), + '--tw-prose-pre-bg': theme('colors.neutral[200]'), + '--tw-prose-th-borders': theme('colors.neutral[300]'), + '--tw-prose-td-borders': theme('colors.neutral[200]'), + '--tw-prose-invert-lead': theme('colors.neutral[300]'), '--tw-prose-invert-links': theme('colors.white'), - '--tw-prose-invert-counters': theme('colors.rose[400]'), - '--tw-prose-invert-bullets': theme('colors.rose[600]'), - '--tw-prose-invert-hr': theme('colors.rose[700]'), - '--tw-prose-invert-quotes': theme('colors.rose[100]'), - '--tw-prose-invert-quote-borders': theme('colors.rose[700]'), - '--tw-prose-invert-captions': theme('colors.rose[400]'), + '--tw-prose-invert-counters': theme('colors.neutral[400]'), + '--tw-prose-invert-bullets': theme('colors.neutral[600]'), + '--tw-prose-invert-hr': theme('colors.neutral[700]'), + '--tw-prose-invert-quotes': theme('colors.neutral[100]'), + '--tw-prose-invert-quote-borders': theme('colors.neutral[700]'), + '--tw-prose-invert-captions': theme('colors.neutral[400]'), '--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-th-borders': theme('colors.rose[600]'), - '--tw-prose-invert-td-borders': theme('colors.rose[700]'), + '--tw-prose-invert-th-borders': theme('colors.neutral[600]'), + '--tw-prose-invert-td-borders': theme('colors.neutral[700]'), }, }, }), diff --git a/lib/chiya/notes.ex b/lib/chiya/notes.ex index e09cf0b..d4d978b 100644 --- a/lib/chiya/notes.ex +++ b/lib/chiya/notes.ex @@ -58,9 +58,11 @@ defmodule Chiya.Notes do Chiya.Flop.validate_and_run(q, params, for: Chiya.Notes.Note) end - def list_apply_notes(regex) do + def list_apply_notes(%Chiya.Tags.Tag{} = tag) do 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() end diff --git a/lib/chiya/notes/note_tag.ex b/lib/chiya/notes/note_tag.ex index fe27e3e..00054ac 100644 --- a/lib/chiya/notes/note_tag.ex +++ b/lib/chiya/notes/note_tag.ex @@ -15,5 +15,6 @@ defmodule Chiya.Notes.NoteTag do note_tag |> cast(attrs, [:note_id, :tag_id]) |> validate_required([:note_id, :tag_id]) + |> unique_constraint([:note_id, :tag_id]) end end diff --git a/lib/chiya/tags.ex b/lib/chiya/tags.ex index a3d16bc..4f8e111 100644 --- a/lib/chiya/tags.ex +++ b/lib/chiya/tags.ex @@ -38,9 +38,10 @@ defmodule Chiya.Tags do |> Repo.all() end - def list_admin_tags(params) do + def list_admin_tags() do q = Tag + |> order_by(:name) |> with_preloads() Repo.all(q) diff --git a/lib/chiya/tags/tag_updater.ex b/lib/chiya/tags/tag_updater.ex index dedfb8a..4272502 100644 --- a/lib/chiya/tags/tag_updater.ex +++ b/lib/chiya/tags/tag_updater.ex @@ -8,6 +8,13 @@ defmodule Chiya.Tags.TagUpdater do alias Chiya.{Notes, Tags} 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 note |> Notes.preload_note() @@ -20,27 +27,19 @@ defmodule Chiya.Tags.TagUpdater do {:error, changeset} end - @doc """ - 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 + def update_tags(%Note{} = note, %{tags_string: new_tags} = attrs) when is_map(attrs) do update_tags(note, new_tags) 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) end - def update_tags(note, attrs) when is_map(attrs) do + def update_tags(%Note{} = note, attrs) when is_map(attrs) do note 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)) end @@ -62,14 +61,7 @@ defmodule Chiya.Tags.TagUpdater do |> remove_tags(old_tags -- new_tags) 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_tags(note, tags) do + def add_tags(note, tags) do tags |> Enum.uniq() |> Enum.each(&add_tag(note, &1)) @@ -77,6 +69,13 @@ defmodule Chiya.Tags.TagUpdater do note 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 slug = Slugger.slugify_downcase(tag) @@ -100,7 +99,13 @@ defmodule Chiya.Tags.TagUpdater do 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 diff --git a/lib/chiya_web/controllers/page_controller.ex b/lib/chiya_web/controllers/page_controller.ex index 86e8cd8..9109e23 100644 --- a/lib/chiya_web/controllers/page_controller.ex +++ b/lib/chiya_web/controllers/page_controller.ex @@ -8,29 +8,26 @@ defmodule ChiyaWeb.PageController do defp put_assigns(conn, opts) do conn |> assign(:page_header, true) + |> assign(:page_title, "no title") end def home(conn, params) do settings = conn.assigns.settings - {channel, notes, meta} = - case settings.home_channel_id do - nil -> - nil + if settings.home_channel_id do + channel = Channels.get_channel!(settings.home_channel_id) + {:ok, {notes, meta}} = Chiya.Notes.list_home_notes(channel, params) - id -> - channel = Channels.get_channel!(id) - {:ok, {notes, meta}} = Chiya.Notes.list_home_notes(channel, params) - {channel, notes, meta} - end - - render(conn, :home, - channel: channel, - notes: notes, - meta: meta, - page_title: "Home", - page_header: false - ) + render(conn, :home, + channel: channel, + notes: notes, + meta: meta, + page_title: "Home", + page_header: false + ) + else + render_error(conn, :not_found) + end end def channel(conn, %{"slug" => channel_slug}) do diff --git a/lib/chiya_web/controllers/page_html.ex b/lib/chiya_web/controllers/page_html.ex index d95c03b..64edeba 100644 --- a/lib/chiya_web/controllers/page_html.ex +++ b/lib/chiya_web/controllers/page_html.ex @@ -1,6 +1,6 @@ defmodule ChiyaWeb.PageHTML do 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/*" diff --git a/lib/chiya_web/controllers/tag_controller.ex b/lib/chiya_web/controllers/tag_controller.ex index 08bc249..781a98f 100644 --- a/lib/chiya_web/controllers/tag_controller.ex +++ b/lib/chiya_web/controllers/tag_controller.ex @@ -2,8 +2,8 @@ defmodule ChiyaWeb.TagController do use ChiyaWeb, :controller alias Chiya.Tags - def index(conn, params) do - tags = Chiya.Tags.list_admin_tags(params) + def index(conn, _params) do + tags = Chiya.Tags.list_admin_tags() render(conn, :index, tags: tags) end @@ -13,13 +13,26 @@ defmodule ChiyaWeb.TagController do render(conn, :show, tag: tag) end - def apply(conn, %{"id" => id}) do + def apply_prepare(conn, %{"id" => id}) do 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) 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 IO.inspect(id) tag = Tags.get_tag!(id) diff --git a/lib/chiya_web/controllers/tag_html/apply_prepare.html.heex b/lib/chiya_web/controllers/tag_html/apply_prepare.html.heex index 22d8dcf..19ee56c 100644 --- a/lib/chiya_web/controllers/tag_html/apply_prepare.html.heex +++ b/lib/chiya_web/controllers/tag_html/apply_prepare.html.heex @@ -1,25 +1,25 @@ <.header> Apply Tag <%= @tag.name %> - <:subtitle>These notes will get the tag. + <:subtitle><%= Enum.count(@notes) %> notes will get the tag. <:actions> - <.link href={~p"/admin/tags/#{@tag}/apply"}> + <.link href={~p"/admin/tags/#{@tag}/apply/run"}> <.button>Apply tag -
+
<%= for note <- @notes do %> -
+
-

+

<%= note.name %>

-
+
Updated <%= from_now(note.updated_at) %> Published <%= from_now(note.published_at) %>
<% end %> -
\ No newline at end of file +
diff --git a/lib/chiya_web/controllers/tag_html/index.html.heex b/lib/chiya_web/controllers/tag_html/index.html.heex index d16959a..baadf2a 100644 --- a/lib/chiya_web/controllers/tag_html/index.html.heex +++ b/lib/chiya_web/controllers/tag_html/index.html.heex @@ -3,17 +3,43 @@ <:subtitle>Tags are tags. -
- <%= for tag <- @tags do %> -
+
+

With Regex

+ + <%= for tag <- Enum.filter(@tags, fn t -> !!t.regex end) do %> +
-

+

<%= tag.name %>

-
+
<%= Enum.count(tag.notes) %> notes Updated <%= from_now(tag.updated_at) %> + <%= if String.length(tag.regex || "") > 0 do %> + Regex: <%= tag.regex %> + <% end %> +
+
+ <% end %> +
+ +
+

Without Regex

+ + <%= for tag <- Enum.filter(@tags, fn t -> !t.regex end) do %> +
+
+

+ <%= tag.name %> +

+
+
<% end %> diff --git a/lib/chiya_web/router.ex b/lib/chiya_web/router.ex index d0f30c3..5f95378 100644 --- a/lib/chiya_web/router.ex +++ b/lib/chiya_web/router.ex @@ -77,7 +77,8 @@ defmodule ChiyaWeb.Router do resources "/tokens", TokenController, only: [:index, :show, :new, :create, :delete] 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 post "/notes/import", NoteController, :import_run