diff --git a/config/quick_tag_table.json b/config/quick_tag_table.json index eed55010..bf9d10f1 100644 --- a/config/quick_tag_table.json +++ b/config/quick_tag_table.json @@ -271,229 +271,643 @@ "pinkie pie" ] }, - "Season 9": { - "1-2": "the beginning of the end", - "3": "uprooted", - "4": "twilight's seven", - "5": "the point of no return", - "6": "common ground", - "7": "she's all yak", - "8": "frenemies (episode)", - "9": "sweet and smoky", - "10": "going to seed", - "11": "student counsel", - "12": "the last crusade (episode)", - "13": "between dark and dawn", - "14": "that's a laugh", - "15": "2 4 6 greaaat", - "16": "a trivial pursuit", - "17": "the summer sun setback", - "18": "she talks to angel", - "19": "dragon dropped", - "20": "a horse shoe-in", - "21": "daring doubt", - "22": "growing up is hard to do", - "23": "the big mac question", - "24-25": "the ending of the end", - "26": "the last problem" - }, - "8": { - "1-2": "school daze", - "3": "the maud couple", - "4": "fake it 'til you make it", - "5": "grannies gone wild", - "6": "surf and/or turf", - "7": "horse play", - "8": "the parent map", - "9": "non-compete clause", - "10": "the break up breakdown", - "11": "molt down", - "12": "marks for effort", - "13": "the mean 6", - "14": "a matter of principals", - "15": "the hearth's warming club", - "16": "friendship university", - "17": "the end in friend", - "18": "yakity-sax", - "19": "on the road to friendship", - "20": "the washouts (episode)", - "21": "a rockhoof and a hard place", - "22": "what lies beneath", - "23": "sounds of silence", - "24": "father knows beast", - "25-26": "school raze", - "Special": "best gift ever" - }, - "7": { - "1": "celestial advice", - "2": "all bottled up", - "3": "a flurry of emotions", - "4": "rock solid friendship", - "5": "fluttershy leans in", - "6": "forever filly", - "7": "parental glideance", - "8": "hard to say anything", - "9": "honest apple", - "10": "a royal problem", - "11": "not asking for trouble", - "12": "discordant harmony", - "13": "the perfect pear", - "14": "fame and misfortune", - "15": "triple threat", - "16": "campfire tales", - "17": "to change a changeling", - "18": "daring done?", - "19": "it isn't the mane thing about you", - "20": "a health of information", - "21": "marks and recreation", - "22": "once upon a zeppelin", - "23": "secrets and pies", - "24": "uncommon bond", - "25-26": "shadow play" - }, - "6": { - "1-2": "the crystalling", - "3": "the gift of the maud pie", - "4": "on your marks", - "5": "gauntlet of fire", - "6": "no second prances", - "7": "newbie dash", - "8": "a hearth's warming tail", - "9": "the saddle row review", - "10": "applejack's \"day\" off", - "11": "flutter brutter", - "12": "spice up your life", - "13": "stranger than fan fiction", - "14": "the cart before the ponies", - "15": "28 pranks later", - "16": "the times they are a changeling", - "17": "dungeons and discords", - "18": "buckball season", - "19": "the fault in our cutie marks", - "20": "viva las pegasus", - "21": "every little thing she does", - "22": "ppov", - "23": "where the apple lies", - "24": "top bolt", - "25-26": "to where and back again" - }, - "5": { - "1-2": "the cutie map", - "3": "castle sweet castle", - "4": "bloom and gloom", - "5": "tanks for the memories", - "6": "appleoosa's most wanted", - "7": "make new friends but keep discord", - "8": "the lost treasure of griffonstone", - "9": "slice of life (episode)", - "10": "princess spike", - "11": "party pooped", - "12": "amending fences", - "13": "do princesses dream of magic sheep", - "14": "canterlot boutique", - "15": "rarity investigates", - "16": "made in manehattan", - "17": "brotherhooves social", - "18": "crusaders of the lost mark", - "19": "the one where pinkie pie knows", - "20": "hearthbreakers", - "21": "scare master", - "22": "what about discord?", - "23": "the hooffields and mccolts", - "24": "the mane attraction", - "25-26": "the cutie remark" - }, - "4": { - "1-2": "princess twilight sparkle (episode)", - "3": "castle mane-ia", - "4": "daring don't", - "5": "flight to the finish", - "6": "power ponies", - "7": "bats!", - "8": "rarity takes manehattan", - "9": "pinkie apple pie", - "10": "rainbow falls", - "11": "three's a crowd", - "12": "pinkie pride", - "13": "simple ways", - "14": "filli vanilli", - "15": "twilight time", - "16": "it ain't easy being breezies", - "17": "somepony to watch over me", - "18": "maud pie (episode)", - "19": "for whom the sweetie belle toils", - "20": "leap of faith", - "21": "testing testing 1-2-3", - "22": "trade ya", - "23": "inspiration manifestation", - "24": "equestria games", - "25-26": "twilight's kingdom" - }, - "3": { - "1-2": "the crystal empire", - "3": "too many pinkie pies", - "4": "one bad apple", - "5": "magic duel", - "6": "sleepless in ponyville", - "7": "wonderbolts academy", - "8": "just for sidekicks", - "9": "apple family reunion", - "10": "spike at your service", - "11": "keep calm and flutter on", - "12": "games ponies play", - "13": "magical mystery cure" - }, - "2": { - "1-2": "the return of harmony", - "3": "lesson zero", - "4": "luna eclipsed", - "5": "sisterhooves social", - "6": "the cutie pox", - "7": "may the best pet win", - "8": "the mysterious mare do well", - "9": "sweet and elite", - "10": "secret of my excess", - "11": "family appreciation day", - "12": "baby cakes", - "13": "hearth's warming eve (episode)", - "14": "the last roundup", - "15": "the super speedy cider squeezy 6000", - "16": "read it and weep", - "17": "hearts and hooves day (episode)", - "18": "a friend in deed", - "19": "putting your hoof down", - "20": "it's about time", - "21": "dragon quest", - "22": "hurricane fluttershy", - "23": "ponyville confidential", - "24": "mmmystery on the friendship express", - "25-26": "a canterlot wedding" - }, - "1": { - "1-2": "friendship is magic", - "3": "the ticket master", - "4": "applebuck season", - "5": "griffon the brush off", - "6": "boast busters", - "7": "dragonshy", - "8": "look before you sleep", - "9": "bridle gossip", - "10": "swarm of the century", - "11": "winter wrap up", - "12": "call of the cutie", - "13": "fall weather friends", - "14": "suited for success", - "15": "feeling pinkie keen", - "16": "sonic rainboom (episode)", - "17": "stare master", - "18": "the show stoppers", - "19": "a dog and pony show", - "20": "green isn't your color", - "21": "over a barrel", - "22": "a bird in the hoof", - "23": "the cutie mark chronicles", - "24": "owl's well that ends well", - "25": "party of one", - "26": "the best night ever" - } -} + "Season 9": [ + { + "1-2": "the beginning of the end" + }, + { + "3": "uprooted" + }, + { + "4": "twilight's seven" + }, + { + "5": "the point of no return" + }, + { + "6": "common ground" + }, + { + "7": "she's all yak" + }, + { + "8": "frenemies (episode)" + }, + { + "9": "sweet and smoky" + }, + { + "10": "going to seed" + }, + { + "11": "student counsel" + }, + { + "12": "the last crusade (episode)" + }, + { + "13": "between dark and dawn" + }, + { + "14": "that's a laugh" + }, + { + "15": "2 4 6 greaaat" + }, + { + "16": "a trivial pursuit" + }, + { + "17": "the summer sun setback" + }, + { + "18": "she talks to angel" + }, + { + "19": "dragon dropped" + }, + { + "20": "a horse shoe-in" + }, + { + "21": "daring doubt" + }, + { + "22": "growing up is hard to do" + }, + { + "23": "the big mac question" + }, + { + "24-25": "the ending of the end" + }, + { + "26": "the last problem" + } + ], + "8": [ + { + "1-2": "school daze" + }, + { + "3": "the maud couple" + }, + { + "4": "fake it 'til you make it" + }, + { + "5": "grannies gone wild" + }, + { + "6": "surf and/or turf" + }, + { + "7": "horse play" + }, + { + "8": "the parent map" + }, + { + "9": "non-compete clause" + }, + { + "10": "the break up breakdown" + }, + { + "11": "molt down" + }, + { + "12": "marks for effort" + }, + { + "13": "the mean 6" + }, + { + "14": "a matter of principals" + }, + { + "15": "the hearth's warming club" + }, + { + "16": "friendship university" + }, + { + "17": "the end in friend" + }, + { + "18": "yakity-sax" + }, + { + "19": "on the road to friendship" + }, + { + "20": "the washouts (episode)" + }, + { + "21": "a rockhoof and a hard place" + }, + { + "22": "what lies beneath" + }, + { + "23": "sounds of silence" + }, + { + "24": "father knows beast" + }, + { + "25-26": "school raze" + }, + { + "Special": "best gift ever" + } + ], + "7": [ + { + "1": "celestial advice" + }, + { + "2": "all bottled up" + }, + { + "3": "a flurry of emotions" + }, + { + "4": "rock solid friendship" + }, + { + "5": "fluttershy leans in" + }, + { + "6": "forever filly" + }, + { + "7": "parental glideance" + }, + { + "8": "hard to say anything" + }, + { + "9": "honest apple" + }, + { + "10": "a royal problem" + }, + { + "11": "not asking for trouble" + }, + { + "12": "discordant harmony" + }, + { + "13": "the perfect pear" + }, + { + "14": "fame and misfortune" + }, + { + "15": "triple threat" + }, + { + "16": "campfire tales" + }, + { + "17": "to change a changeling" + }, + { + "18": "daring done?" + }, + { + "19": "it isn't the mane thing about you" + }, + { + "20": "a health of information" + }, + { + "21": "marks and recreation" + }, + { + "22": "once upon a zeppelin" + }, + { + "23": "secrets and pies" + }, + { + "24": "uncommon bond" + }, + { + "25-26": "shadow play" + } + ], + "6": [ + { + "1-2": "the crystalling" + }, + { + "3": "the gift of the maud pie" + }, + { + "4": "on your marks" + }, + { + "5": "gauntlet of fire" + }, + { + "6": "no second prances" + }, + { + "7": "newbie dash" + }, + { + "8": "a hearth's warming tail" + }, + { + "9": "the saddle row review" + }, + { + "10": "applejack's \"day\" off" + }, + { + "11": "flutter brutter" + }, + { + "12": "spice up your life" + }, + { + "13": "stranger than fan fiction" + }, + { + "14": "the cart before the ponies" + }, + { + "15": "28 pranks later" + }, + { + "16": "the times they are a changeling" + }, + { + "17": "dungeons and discords" + }, + { + "18": "buckball season" + }, + { + "19": "the fault in our cutie marks" + }, + { + "20": "viva las pegasus" + }, + { + "21": "every little thing she does" + }, + { + "22": "ppov" + }, + { + "23": "where the apple lies" + }, + { + "24": "top bolt" + }, + { + "25-26": "to where and back again" + } + ], + "5": [ + { + "1-2": "the cutie map" + }, + { + "3": "castle sweet castle" + }, + { + "4": "bloom and gloom" + }, + { + "5": "tanks for the memories" + }, + { + "6": "appleoosa's most wanted" + }, + { + "7": "make new friends but keep discord" + }, + { + "8": "the lost treasure of griffonstone" + }, + { + "9": "slice of life (episode)" + }, + { + "10": "princess spike" + }, + { + "11": "party pooped" + }, + { + "12": "amending fences" + }, + { + "13": "do princesses dream of magic sheep" + }, + { + "14": "canterlot boutique" + }, + { + "15": "rarity investigates" + }, + { + "16": "made in manehattan" + }, + { + "17": "brotherhooves social" + }, + { + "18": "crusaders of the lost mark" + }, + { + "19": "the one where pinkie pie knows" + }, + { + "20": "hearthbreakers" + }, + { + "21": "scare master" + }, + { + "22": "what about discord?" + }, + { + "23": "the hooffields and mccolts" + }, + { + "24": "the mane attraction" + }, + { + "25-26": "the cutie remark" + } + ], + "4": [ + { + "1-2": "princess twilight sparkle (episode)" + }, + { + "3": "castle mane-ia" + }, + { + "4": "daring don't" + }, + { + "5": "flight to the finish" + }, + { + "6": "power ponies" + }, + { + "7": "bats!" + }, + { + "8": "rarity takes manehattan" + }, + { + "9": "pinkie apple pie" + }, + { + "10": "rainbow falls" + }, + { + "11": "three's a crowd" + }, + { + "12": "pinkie pride" + }, + { + "13": "simple ways" + }, + { + "14": "filli vanilli" + }, + { + "15": "twilight time" + }, + { + "16": "it ain't easy being breezies" + }, + { + "17": "somepony to watch over me" + }, + { + "18": "maud pie (episode)" + }, + { + "19": "for whom the sweetie belle toils" + }, + { + "20": "leap of faith" + }, + { + "21": "testing testing 1-2-3" + }, + { + "22": "trade ya" + }, + { + "23": "inspiration manifestation" + }, + { + "24": "equestria games" + }, + { + "25-26": "twilight's kingdom" + } + ], + "3": [ + { + "1-2": "the crystal empire" + }, + { + "3": "too many pinkie pies" + }, + { + "4": "one bad apple" + }, + { + "5": "magic duel" + }, + { + "6": "sleepless in ponyville" + }, + { + "7": "wonderbolts academy" + }, + { + "8": "just for sidekicks" + }, + { + "9": "apple family reunion" + }, + { + "10": "spike at your service" + }, + { + "11": "keep calm and flutter on" + }, + { + "12": "games ponies play" + }, + { + "13": "magical mystery cure" + } + ], + "2": [ + { + "1-2": "the return of harmony" + }, + { + "3": "lesson zero" + }, + { + "4": "luna eclipsed" + }, + { + "5": "sisterhooves social" + }, + { + "6": "the cutie pox" + }, + { + "7": "may the best pet win" + }, + { + "8": "the mysterious mare do well" + }, + { + "9": "sweet and elite" + }, + { + "10": "secret of my excess" + }, + { + "11": "family appreciation day" + }, + { + "12": "baby cakes" + }, + { + "13": "hearth's warming eve (episode)" + }, + { + "14": "the last roundup" + }, + { + "15": "the super speedy cider squeezy 6000" + }, + { + "16": "read it and weep" + }, + { + "17": "hearts and hooves day (episode)" + }, + { + "18": "a friend in deed" + }, + { + "19": "putting your hoof down" + }, + { + "20": "it's about time" + }, + { + "21": "dragon quest" + }, + { + "22": "hurricane fluttershy" + }, + { + "23": "ponyville confidential" + }, + { + "24": "mmmystery on the friendship express" + }, + { + "25-26": "a canterlot wedding" + } + ], + "1": [ + { + "1-2": "friendship is magic" + }, + { + "3": "the ticket master" + }, + { + "4": "applebuck season" + }, + { + "5": "griffon the brush off" + }, + { + "6": "boast busters" + }, + { + "7": "dragonshy" + }, + { + "8": "look before you sleep" + }, + { + "9": "bridle gossip" + }, + { + "10": "swarm of the century" + }, + { + "11": "winter wrap up" + }, + { + "12": "call of the cutie" + }, + { + "13": "fall weather friends" + }, + { + "14": "suited for success" + }, + { + "15": "feeling pinkie keen" + }, + { + "16": "sonic rainboom (episode)" + }, + { + "17": "stare master" + }, + { + "18": "the show stoppers" + }, + { + "19": "a dog and pony show" + }, + { + "20": "green isn't your color" + }, + { + "21": "over a barrel" + }, + { + "22": "a bird in the hoof" + }, + { + "23": "the cutie mark chronicles" + }, + { + "24": "owl's well that ends well" + }, + { + "25": "party of one" + }, + { + "26": "the best night ever" + } + ] +} \ No newline at end of file diff --git a/lib/philomena_web/templates/image/_tags.html.slime b/lib/philomena_web/templates/image/_tags.html.slime index 8350504b..30650752 100644 --- a/lib/philomena_web/templates/image/_tags.html.slime +++ b/lib/philomena_web/templates/image/_tags.html.slime @@ -42,6 +42,9 @@ button.button.js-tag-sauce-toggle data-click-toggle=".tagsauce, .js-imageform" data-click-focus=".js-taginput-plain:not(.hidden), .js-taginput-input" ' Cancel + .block.js-tagtable data-target='[name="image[tag_input]"]' + = PhilomenaWeb.TagView.quick_tags(@conn) + - else p ' You can't edit the tags on this image. diff --git a/lib/philomena_web/templates/image/new.html.slime b/lib/philomena_web/templates/image/new.html.slime index 22a7fb41..3bc9c1fd 100644 --- a/lib/philomena_web/templates/image/new.html.slime +++ b/lib/philomena_web/templates/image/new.html.slime @@ -61,6 +61,11 @@ button.button.button--state-warning.button--separate-left.button--bold id="tagsinput-load" type="button" title="This button loads any saved tags from your browser" Load button.button.button--state-danger.button--separate-left.button--bold id="tagsinput-clear" type="button" title="This button will clear the list of tags above" Clear + p You can mouse over tags below to view a description, and click to add. Short tag names can be used and will expand to full. + + .block.js-tagtable data-target='[name="image[tag_input]"]' + = PhilomenaWeb.TagView.quick_tags(@conn) + br .field diff --git a/lib/philomena_web/templates/search/_form.html.slime b/lib/philomena_web/templates/search/_form.html.slime index 7f51af60..8f3fa853 100644 --- a/lib/philomena_web/templates/search/_form.html.slime +++ b/lib/philomena_web/templates/search/_form.html.slime @@ -35,9 +35,9 @@ h1 Search a data-search-add="my:upvotes" data-search-show-help=" " My upvotes a data-search-add="my:uploads" data-search-show-help=" " My uploads a data-search-add="my:watched" data-search-show-help=" " My watched tags - /.flex__right + .flex__right a href="#" data-click-toggle="#js-search-tags" Quick tags - a href=search_syntax_path Help + a href="/search/syntax" Help .block__content .hidden.walloftext data-search-help="numeric" @@ -93,8 +93,9 @@ h1 Search code<> source_url:*deviantart.com* | . - /#js-search-tags.hidden - /= render partial: 'tags/quick_tag_table', locals: { target: '.js-search-field' } + #js-search-tags.hidden + .block.js-tagtable data-target=".js-search-field" + = PhilomenaWeb.TagView.quick_tags(@conn) .field.field--inline.flex-wrap = submit "Search", class: "button button--state-primary" diff --git a/lib/philomena_web/templates/tag/_quick_tag_table.html.slime b/lib/philomena_web/templates/tag/_quick_tag_table.html.slime new file mode 100644 index 00000000..bd938942 --- /dev/null +++ b/lib/philomena_web/templates/tag/_quick_tag_table.html.slime @@ -0,0 +1,14 @@ +elixir: + tabs = Enum.with_index(@data["tabs"]) + tab_modes = @data["tab_modes"] + +.block__header--sub.block__header--js--tabbed + = for {name, i} <- tabs do + = link name, to: "#", class: tab_class(i), data: [click_tab: name] + += for {name, i} <- tabs do + - tab_data = @data[name] + - tab_mode = tab_modes[name] + + .block__tab.quick-tag-table__tab class=tab_body_class(i) data-tab=name + = render PhilomenaWeb.TagView, "_quick_tag_table_#{tab_mode}.html", tab: name, data: tab_data, shipping: @shipping, tags: @tags, conn: @conn \ No newline at end of file diff --git a/lib/philomena_web/templates/tag/_quick_tag_table_default.html.slime b/lib/philomena_web/templates/tag/_quick_tag_table_default.html.slime new file mode 100644 index 00000000..8dcac0c2 --- /dev/null +++ b/lib/philomena_web/templates/tag/_quick_tag_table_default.html.slime @@ -0,0 +1,8 @@ += for {heading, tag_names} <- @data do + div + strong = heading + br + + = for tag_name <- tag_names do + = tag_link @tags[tag_name], tag_name + br \ No newline at end of file diff --git a/lib/philomena_web/templates/tag/_quick_tag_table_season.html.slime b/lib/philomena_web/templates/tag/_quick_tag_table_season.html.slime new file mode 100644 index 00000000..963a9196 --- /dev/null +++ b/lib/philomena_web/templates/tag/_quick_tag_table_season.html.slime @@ -0,0 +1,9 @@ += for slice <- Enum.chunk_every(@data, 10) do + div + = for map <- slice do + - [{header, tag_name}] = Enum.to_list(map) + + = header + ' . + = tag_link @tags[tag_name], tag_name + br \ No newline at end of file diff --git a/lib/philomena_web/templates/tag/_quick_tag_table_shipping.html.slime b/lib/philomena_web/templates/tag/_quick_tag_table_shipping.html.slime new file mode 100644 index 00000000..b85f480a --- /dev/null +++ b/lib/philomena_web/templates/tag/_quick_tag_table_shipping.html.slime @@ -0,0 +1,5 @@ += for slice <- Enum.chunk_every(@shipping[@tab], 10) do + div + = for tag_name <- slice do + = tag_link @tags[tag_name], tag_name + br \ No newline at end of file diff --git a/lib/philomena_web/templates/tag/_quick_tag_table_shorthand.html.slime b/lib/philomena_web/templates/tag/_quick_tag_table_shorthand.html.slime new file mode 100644 index 00000000..9385a74b --- /dev/null +++ b/lib/philomena_web/templates/tag/_quick_tag_table_shorthand.html.slime @@ -0,0 +1,10 @@ += for {heading, maps} <- @data do + div + strong = heading + br + + = for {name, alias_name} <- maps do + => name + ' - + = tag_link @tags[alias_name], alias_name + br \ No newline at end of file diff --git a/lib/philomena_web/views/tag_view.ex b/lib/philomena_web/views/tag_view.ex index 08d8f02d..2388a4e4 100644 --- a/lib/philomena_web/views/tag_view.ex +++ b/lib/philomena_web/views/tag_view.ex @@ -1,3 +1,120 @@ defmodule PhilomenaWeb.TagView do use PhilomenaWeb, :view + + # this is bad practice, don't copy this. + alias Philomena.Tags.Implication + alias Philomena.Tags.Tag + alias Philomena.Repo + import Ecto.Query + + def quick_tags(conn) do + case Application.get_env(:philomena, :quick_tags) do + nil -> + quick_tags = + Application.get_env(:philomena, :quick_tags_json) + |> Jason.decode!() + |> lookup_quick_tags() + |> render_quick_tags(conn) + + Application.put_env(:philomena, :quick_tags, quick_tags) + + quick_tags + + quick_tags -> + quick_tags + end + end + + def tab_class(0), do: "selected" + def tab_class(_), do: nil + + def tab_body_class(0), do: nil + def tab_body_class(_), do: "hidden" + + def tag_link(nil, tag_name), do: tag_name + def tag_link(tag, tag_name) do + title = title(implications(tag) ++ short_description(tag)) + + link tag_name, to: "#", title: title, data: [tag_name: tag_name, click_addtag: tag_name] + end + + defp implications(%{implied_tags: []}), do: [] + defp implications(%{implied_tags: it}) do + names = + it + |> Enum.map(& &1.name) + |> Enum.sort() + |> Enum.join(", ") + + ["Implies: #{names}"] + end + + defp short_description(%{short_description: s}) when s in ["", nil], do: [] + defp short_description(%{short_description: s}), do: [s] + + defp title([]), do: nil + defp title(descriptions), do: Enum.join(descriptions, "\n") + + defp lookup_quick_tags(%{"tabs" => tabs, "tab_modes" => tab_modes} = data) do + tags = + tabs + |> Enum.flat_map(&names_in_tab(tab_modes[&1], data[&1])) + |> tags_indexed_by_name() + + shipping = + tabs + |> Enum.filter(&tab_modes[&1] == "shipping") + |> Map.new(fn tab -> + sd = data[tab] + + {tab, implied_by_multitag(sd["implying"], sd["not_implying"])} + end) + + {tags, shipping, data} + end + + defp render_quick_tags({tags, shipping, data}, conn) do + render PhilomenaWeb.TagView, "_quick_tag_table.html", tags: tags, shipping: shipping, data: data, conn: conn + end + + defp names_in_tab("default", data) do + Map.values(data) + |> List.flatten() + end + + defp names_in_tab("season", data) do + data + |> Enum.flat_map(&Map.values/1) + end + + defp names_in_tab("shorthand", data) do + data + |> Map.values() + |> Enum.flat_map(&Map.values/1) + end + + defp names_in_tab(_mode, _data), do: [] + + defp tags_indexed_by_name(names) do + Tag + |> where([t], t.name in ^names) + |> preload(:implied_tags) + |> Repo.all() + |> Map.new(&{&1.name, &1}) + end + + defp implied_by_multitag(tag_names, ignore_tag_names) do + query = + from t in Tag, + left_join: i in Implication, + on: t.id == i.tag_id, + left_join: it in Tag, + on: it.id == i.implied_tag_id, + where: it.name in ^tag_names and it.name not in ^ignore_tag_names, + order_by: [desc: t.images_count], + preload: :implied_tags, + limit: 40 + + Repo.all(query) + end end