mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
mix format
This commit is contained in:
parent
e864a99a43
commit
4e9cde610a
12 changed files with 141 additions and 89 deletions
|
@ -3,7 +3,9 @@ defmodule Philomena.Markdown do
|
||||||
|
|
||||||
# When your NIF is loaded, it will override this function.
|
# When your NIF is loaded, it will override this function.
|
||||||
def to_html(text, replacements), do: Philomena.Native.markdown_to_html(text, replacements)
|
def to_html(text, replacements), do: Philomena.Native.markdown_to_html(text, replacements)
|
||||||
def to_html_unsafe(text, replacements), do: Philomena.Native.markdown_to_html_unsafe(text, replacements)
|
|
||||||
|
def to_html_unsafe(text, replacements),
|
||||||
|
do: Philomena.Native.markdown_to_html_unsafe(text, replacements)
|
||||||
|
|
||||||
def escape_markdown(text) do
|
def escape_markdown(text) do
|
||||||
@markdown_chars
|
@markdown_chars
|
||||||
|
|
|
@ -2,9 +2,9 @@ defmodule Philomena.Textile.Lexer do
|
||||||
import NimbleParsec
|
import NimbleParsec
|
||||||
|
|
||||||
token_list =
|
token_list =
|
||||||
Enum.to_list(0x01..0x29)
|
Enum.to_list(0x01..0x29) ++
|
||||||
++ Enum.to_list(0x2b..0x2f)
|
Enum.to_list(0x2B..0x2F) ++
|
||||||
++ ':;<=>?[]\\^`~|'
|
':;<=>?[]\\^`~|'
|
||||||
|
|
||||||
space_list = '\f \r\t\u00a0\u1680\u180e\u202f\u205f\u3000' ++ Enum.to_list(0x2000..0x200A)
|
space_list = '\f \r\t\u00a0\u1680\u180e\u202f\u205f\u3000' ++ Enum.to_list(0x2000..0x200A)
|
||||||
space = utf8_char(space_list)
|
space = utf8_char(space_list)
|
||||||
|
|
|
@ -87,21 +87,44 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
#
|
#
|
||||||
# open_token callback* close_token
|
# open_token callback* close_token
|
||||||
#
|
#
|
||||||
defp simple_recursive(:bq_open, :bq_close, open_tag, close_tag, callback, parser, [
|
defp simple_recursive(
|
||||||
{:bq_open, open} | r_tokens
|
:bq_open,
|
||||||
], level) do
|
:bq_close,
|
||||||
|
open_tag,
|
||||||
|
close_tag,
|
||||||
|
callback,
|
||||||
|
parser,
|
||||||
|
[
|
||||||
|
{:bq_open, open} | r_tokens
|
||||||
|
],
|
||||||
|
level
|
||||||
|
) do
|
||||||
case repeat(callback, parser, r_tokens, true, level) do
|
case repeat(callback, parser, r_tokens, true, level) do
|
||||||
{:ok, tree, [{:bq_close, _} | r2_tokens], level} ->
|
{:ok, tree, [{:bq_close, _} | r2_tokens], level} ->
|
||||||
{:ok, [{:markup, "\n" <> String.duplicate(open_tag, level)}, tree, {:markup, "\n" <> String.duplicate(close_tag, level - 1)}], r2_tokens}
|
{:ok,
|
||||||
|
[
|
||||||
|
{:markup, "\n" <> String.duplicate(open_tag, level)},
|
||||||
|
tree,
|
||||||
|
{:markup, "\n" <> String.duplicate(close_tag, level - 1)}
|
||||||
|
], r2_tokens}
|
||||||
|
|
||||||
{:ok, tree, r2_tokens, level} ->
|
{:ok, tree, r2_tokens, level} ->
|
||||||
{:ok, [{:text, open}, tree], r2_tokens}
|
{:ok, [{:text, open}, tree], r2_tokens}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp simple_recursive(open_token, close_token, open_tag, close_tag, callback, parser, [
|
defp simple_recursive(
|
||||||
{open_token, open} | r_tokens
|
open_token,
|
||||||
], level) do
|
close_token,
|
||||||
|
open_tag,
|
||||||
|
close_tag,
|
||||||
|
callback,
|
||||||
|
parser,
|
||||||
|
[
|
||||||
|
{open_token, open} | r_tokens
|
||||||
|
],
|
||||||
|
level
|
||||||
|
) do
|
||||||
case repeat(callback, parser, r_tokens, false, level) do
|
case repeat(callback, parser, r_tokens, false, level) do
|
||||||
{:ok, tree, [{^close_token, _} | r2_tokens]} ->
|
{:ok, tree, [{^close_token, _} | r2_tokens]} ->
|
||||||
{:ok, [{:markup, open_tag}, tree, {:markup, close_tag}], r2_tokens}
|
{:ok, [{:markup, open_tag}, tree, {:markup, close_tag}], r2_tokens}
|
||||||
|
@ -152,8 +175,7 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
_ ->
|
_ ->
|
||||||
case repeat(callback, put_state(parser, state), r_tokens, false, level) do
|
case repeat(callback, put_state(parser, state), r_tokens, false, level) do
|
||||||
{:ok, tree, [{^close_token, close}, {^lookahead_not, ln} | r2_tokens]} ->
|
{:ok, tree, [{^close_token, close}, {^lookahead_not, ln} | r2_tokens]} ->
|
||||||
{:ok, [{:text, open}, tree, {:text, close}],
|
{:ok, [{:text, open}, tree, {:text, close}], [{lookahead_not, ln} | r2_tokens]}
|
||||||
[{lookahead_not, ln} | r2_tokens]}
|
|
||||||
|
|
||||||
{:ok, tree, [{^close_token, _} | r2_tokens]} ->
|
{:ok, tree, [{^close_token, _} | r2_tokens]} ->
|
||||||
{:ok, [{:markup, open_tag}, tree, {:markup, close_tag}], r2_tokens}
|
{:ok, [{:markup, open_tag}, tree, {:markup, close_tag}], r2_tokens}
|
||||||
|
@ -261,9 +283,7 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
{:ok, tree, [{:unbracketed_link_url, <<"\":", url::binary>>} | r2_tokens]} ->
|
{:ok, tree, [{:unbracketed_link_url, <<"\":", url::binary>>} | r2_tokens]} ->
|
||||||
href = url
|
href = url
|
||||||
|
|
||||||
{:ok,
|
{:ok, [{:markup, "["}, tree, {:markup, "]("}, {:markup, href}, {:markup, ")"}], r2_tokens}
|
||||||
[{:markup, "["}, tree, {:markup, "]("}, {:markup, href}, {:markup, ")"}],
|
|
||||||
r2_tokens}
|
|
||||||
|
|
||||||
{:ok, tree, r2_tokens} ->
|
{:ok, tree, r2_tokens} ->
|
||||||
{:ok, [{:text, open}, tree], r2_tokens}
|
{:ok, [{:text, open}, tree], r2_tokens}
|
||||||
|
@ -275,19 +295,21 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
{:ok, tree, [{:bracketed_link_url, <<"\":", url::binary>>} | r2_tokens]} ->
|
{:ok, tree, [{:bracketed_link_url, <<"\":", url::binary>>} | r2_tokens]} ->
|
||||||
href = url
|
href = url
|
||||||
|
|
||||||
{:ok,
|
{:ok, [{:markup, "["}, tree, {:markup, "]("}, {:markup, href}, {:markup, ")"}], r2_tokens}
|
||||||
[{:markup, "["}, tree, {:markup, "]("}, {:markup, href}, {:markup, ")"}],
|
|
||||||
r2_tokens}
|
|
||||||
|
|
||||||
{:ok, tree, r2_tokens} ->
|
{:ok, tree, r2_tokens} ->
|
||||||
{:ok, [{:text, open}, tree], r2_tokens}
|
{:ok, [{:text, open}, tree], r2_tokens}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inner_inline_textile_element(parser, [
|
defp inner_inline_textile_element(
|
||||||
{token, img},
|
parser,
|
||||||
{:unbracketed_image_url, <<":", url::binary>>} | r_tokens
|
[
|
||||||
], level)
|
{token, img},
|
||||||
|
{:unbracketed_image_url, <<":", url::binary>>} | r_tokens
|
||||||
|
],
|
||||||
|
level
|
||||||
|
)
|
||||||
when token in [:unbracketed_image, :bracketed_image] do
|
when token in [:unbracketed_image, :bracketed_image] do
|
||||||
img = parser.image_transform.(img)
|
img = parser.image_transform.(img)
|
||||||
|
|
||||||
|
@ -372,8 +394,7 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
#
|
#
|
||||||
|
|
||||||
defp inline_textile_element_not_opening_markup(_parser, [{:literal, lit} | r_tokens], level) do
|
defp inline_textile_element_not_opening_markup(_parser, [{:literal, lit} | r_tokens], level) do
|
||||||
{:ok, [{:markup, Markdown.escape_markdown(lit)},],
|
{:ok, [{:markup, Markdown.escape_markdown(lit)}], r_tokens}
|
||||||
r_tokens}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inline_textile_element_not_opening_markup(_parser, [{:space, _} | r_tokens], level) do
|
defp inline_textile_element_not_opening_markup(_parser, [{:space, _} | r_tokens], level) do
|
||||||
|
@ -385,11 +406,15 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
{:ok, [{:text, binary}], r2_tokens}
|
{:ok, [{:text, binary}], r2_tokens}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inline_textile_element_not_opening_markup(_parser, [
|
defp inline_textile_element_not_opening_markup(
|
||||||
{:quicktxt, q1},
|
_parser,
|
||||||
{token, t},
|
[
|
||||||
{:quicktxt, q2} | r_tokens
|
{:quicktxt, q1},
|
||||||
], level)
|
{token, t},
|
||||||
|
{:quicktxt, q2} | r_tokens
|
||||||
|
],
|
||||||
|
level
|
||||||
|
)
|
||||||
when token in [
|
when token in [
|
||||||
:b_delim,
|
:b_delim,
|
||||||
:i_delim,
|
:i_delim,
|
||||||
|
@ -400,15 +425,18 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
:del_delim,
|
:del_delim,
|
||||||
:sub_delim
|
:sub_delim
|
||||||
] do
|
] do
|
||||||
{:ok, [{:text, <<q1::utf8>>}, {:text, t}, {:text, <<q2::utf8>>}],
|
{:ok, [{:text, <<q1::utf8>>}, {:text, t}, {:text, <<q2::utf8>>}], r_tokens}
|
||||||
r_tokens}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inline_textile_element_not_opening_markup(_parser, [{:quicktxt, lit} | r_tokens], level) do
|
defp inline_textile_element_not_opening_markup(_parser, [{:quicktxt, lit} | r_tokens], level) do
|
||||||
{:ok, [{:text, <<lit::utf8>>}], r_tokens}
|
{:ok, [{:text, <<lit::utf8>>}], r_tokens}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inline_textile_element_not_opening_markup(parser, [{:bq_cite_start, start} | r_tokens], level) do
|
defp inline_textile_element_not_opening_markup(
|
||||||
|
parser,
|
||||||
|
[{:bq_cite_start, start} | r_tokens],
|
||||||
|
level
|
||||||
|
) do
|
||||||
case repeat(&bq_cite_text/3, parser, r_tokens, false, level) do
|
case repeat(&bq_cite_text/3, parser, r_tokens, false, level) do
|
||||||
{:ok, tree, [{:bq_cite_open, open} | r2_tokens]} ->
|
{:ok, tree, [{:bq_cite_open, open} | r2_tokens]} ->
|
||||||
case repeat(&block_textile_element/4, parser, r2_tokens, true, level + 1) do
|
case repeat(&block_textile_element/4, parser, r2_tokens, true, level + 1) do
|
||||||
|
@ -437,7 +465,11 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp inline_textile_element_not_opening_markup(_parser, [{:bq_cite_open, tok} | r_tokens], level) do
|
defp inline_textile_element_not_opening_markup(
|
||||||
|
_parser,
|
||||||
|
[{:bq_cite_open, tok} | r_tokens],
|
||||||
|
level
|
||||||
|
) do
|
||||||
{:ok, [{:text, tok}], r_tokens}
|
{:ok, [{:text, tok}], r_tokens}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -495,7 +527,8 @@ defmodule Philomena.Textile.ParserMarkdown do
|
||||||
# double_newline | newline | inline_textile_element;
|
# double_newline | newline | inline_textile_element;
|
||||||
#
|
#
|
||||||
|
|
||||||
defp block_textile_element(_parser, [{:double_newline, _} | r_tokens], bq, level) when bq == true do
|
defp block_textile_element(_parser, [{:double_newline, _} | r_tokens], bq, level)
|
||||||
|
when bq == true do
|
||||||
one = "\n" <> String.duplicate("> ", level)
|
one = "\n" <> String.duplicate("> ", level)
|
||||||
{:ok, [{:markup, String.duplicate(one, 2)}], r_tokens}
|
{:ok, [{:markup, String.duplicate(one, 2)}], r_tokens}
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,7 +34,8 @@ defmodule PhilomenaWeb.Image.DescriptionController do
|
||||||
|
|
||||||
Images.reindex_image(image)
|
Images.reindex_image(image)
|
||||||
|
|
||||||
body = TextRenderer.render_one(%{body: image.description, body_md: image.description_md}, conn)
|
body =
|
||||||
|
TextRenderer.render_one(%{body: image.description, body_md: image.description_md}, conn)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_view(PhilomenaWeb.ImageView)
|
|> put_view(PhilomenaWeb.ImageView)
|
||||||
|
|
|
@ -134,9 +134,11 @@ defmodule PhilomenaWeb.ProfileController do
|
||||||
|> TextRenderer.render_collection(conn)
|
|> TextRenderer.render_collection(conn)
|
||||||
|> Enum.zip(recent_comments)
|
|> Enum.zip(recent_comments)
|
||||||
|
|
||||||
about_me = TextRenderer.render_one(%{body_md: user.description_md, body: user.description || ""}, conn)
|
about_me =
|
||||||
|
TextRenderer.render_one(%{body_md: user.description_md, body: user.description || ""}, conn)
|
||||||
|
|
||||||
scratchpad = TextRenderer.render_one(%{body_md: user.scratchpad_md, body: user.scratchpad || ""}, conn)
|
scratchpad =
|
||||||
|
TextRenderer.render_one(%{body_md: user.scratchpad_md, body: user.scratchpad || ""}, conn)
|
||||||
|
|
||||||
commission_information = commission_info(user.commission, conn)
|
commission_information = commission_info(user.commission, conn)
|
||||||
|
|
||||||
|
@ -214,8 +216,9 @@ defmodule PhilomenaWeb.ProfileController do
|
||||||
defp map_fetch(nil, _field_name), do: nil
|
defp map_fetch(nil, _field_name), do: nil
|
||||||
defp map_fetch(map, field_name), do: Map.get(map, field_name)
|
defp map_fetch(map, field_name), do: Map.get(map, field_name)
|
||||||
|
|
||||||
defp commission_info(%{information: info, information_md: info_md}, conn) when info not in [nil, ""],
|
defp commission_info(%{information: info, information_md: info_md}, conn)
|
||||||
do: TextRenderer.render_one(%{body: info, body_md: info_md}, conn)
|
when info not in [nil, ""],
|
||||||
|
do: TextRenderer.render_one(%{body: info, body_md: info_md}, conn)
|
||||||
|
|
||||||
defp commission_info(_commission, _conn), do: ""
|
defp commission_info(_commission, _conn), do: ""
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,8 @@ defmodule PhilomenaWeb.TagController do
|
||||||
|
|
||||||
interactions = Interactions.user_interactions(images, user)
|
interactions = Interactions.user_interactions(images, user)
|
||||||
|
|
||||||
body = TextRenderer.render_one(%{body_md: tag.description_md, body: tag.description || ""}, conn)
|
body =
|
||||||
|
TextRenderer.render_one(%{body_md: tag.description_md, body: tag.description || ""}, conn)
|
||||||
|
|
||||||
dnp_bodies =
|
dnp_bodies =
|
||||||
TextRenderer.render_collection(
|
TextRenderer.render_collection(
|
||||||
|
|
|
@ -139,7 +139,8 @@ defmodule PhilomenaWeb.ImageLoader do
|
||||||
|
|
||||||
dnp_entries = Enum.zip(dnp_bodies, tag.dnp_entries)
|
dnp_entries = Enum.zip(dnp_bodies, tag.dnp_entries)
|
||||||
|
|
||||||
description = TextRenderer.render_one(%{body_md: tag.description_md, body: tag.description || ""}, conn)
|
description =
|
||||||
|
TextRenderer.render_one(%{body_md: tag.description_md, body: tag.description || ""}, conn)
|
||||||
|
|
||||||
[{tag, description, dnp_entries}]
|
[{tag, description, dnp_entries}]
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,7 +17,9 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
|
|
||||||
defp find_images(text) do
|
defp find_images(text) do
|
||||||
Regex.scan(~r/>>(\d+)([tsp])?/, text, capture: :all_but_first)
|
Regex.scan(~r/>>(\d+)([tsp])?/, text, capture: :all_but_first)
|
||||||
|> Enum.map(fn matches -> [Enum.at(matches, 0) |> String.to_integer(), Enum.at(matches, 1) || ""] end)
|
|> Enum.map(fn matches ->
|
||||||
|
[Enum.at(matches, 0) |> String.to_integer(), Enum.at(matches, 1) || ""]
|
||||||
|
end)
|
||||||
|> Enum.filter(fn m -> Enum.at(m, 0) < 2_147_483_647 end)
|
|> Enum.filter(fn m -> Enum.at(m, 0) < 2_147_483_647 end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -52,42 +54,50 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
img = loaded_images[Enum.at(group, 0)]
|
img = loaded_images[Enum.at(group, 0)]
|
||||||
text = "#{Enum.at(group, 0)}#{Enum.at(group, 1)}"
|
text = "#{Enum.at(group, 0)}#{Enum.at(group, 1)}"
|
||||||
|
|
||||||
rendered = cond do
|
rendered =
|
||||||
img != nil ->
|
cond do
|
||||||
case group do
|
img != nil ->
|
||||||
[_id, "p"] when not img.hidden_from_users ->
|
case group do
|
||||||
Phoenix.View.render(@image_view, "_image_target.html",
|
[_id, "p"] when not img.hidden_from_users ->
|
||||||
image: img,
|
Phoenix.View.render(@image_view, "_image_target.html",
|
||||||
size: :medium,
|
image: img,
|
||||||
conn: conn
|
size: :medium,
|
||||||
)
|
conn: conn
|
||||||
|> safe_to_string()
|
)
|
||||||
[_id, "t"] when not img.hidden_from_users ->
|
|> safe_to_string()
|
||||||
Phoenix.View.render(@image_view, "_image_target.html",
|
|
||||||
image: img,
|
[_id, "t"] when not img.hidden_from_users ->
|
||||||
size: :small,
|
Phoenix.View.render(@image_view, "_image_target.html",
|
||||||
conn: conn
|
image: img,
|
||||||
)
|
size: :small,
|
||||||
|> safe_to_string()
|
conn: conn
|
||||||
[_id, "s"] when not img.hidden_from_users ->
|
)
|
||||||
Phoenix.View.render(@image_view, "_image_target.html",
|
|> safe_to_string()
|
||||||
image: img,
|
|
||||||
size: :thumb_small,
|
[_id, "s"] when not img.hidden_from_users ->
|
||||||
conn: conn
|
Phoenix.View.render(@image_view, "_image_target.html",
|
||||||
)
|
image: img,
|
||||||
|> safe_to_string()
|
size: :thumb_small,
|
||||||
[_id, ""] ->
|
conn: conn
|
||||||
link(">>#{img.id}#{link_suffix(img)}", to: "/images/#{img.id}")
|
)
|
||||||
|> safe_to_string()
|
|> safe_to_string()
|
||||||
[_id, suffix] when suffix in ["t", "s", "p"] ->
|
|
||||||
link(">>#{img.id}#{suffix}#{link_suffix(img)}", to: "/images/#{img.id}")
|
[_id, ""] ->
|
||||||
|> safe_to_string()
|
link(">>#{img.id}#{link_suffix(img)}", to: "/images/#{img.id}")
|
||||||
[id, suffix] -> # This condition should never trigger, but let's leave it here just in case.
|
|> safe_to_string()
|
||||||
">>#{id}#{suffix}"
|
|
||||||
end
|
[_id, suffix] when suffix in ["t", "s", "p"] ->
|
||||||
true ->
|
link(">>#{img.id}#{suffix}#{link_suffix(img)}", to: "/images/#{img.id}")
|
||||||
">>#{text}"
|
|> safe_to_string()
|
||||||
end
|
|
||||||
|
# This condition should never trigger, but let's leave it here just in case.
|
||||||
|
[id, suffix] ->
|
||||||
|
">>#{id}#{suffix}"
|
||||||
|
end
|
||||||
|
|
||||||
|
true ->
|
||||||
|
">>#{text}"
|
||||||
|
end
|
||||||
|
|
||||||
[text, rendered]
|
[text, rendered]
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
defmodule PhilomenaWeb.ErrorView do
|
defmodule PhilomenaWeb.ErrorView do
|
||||||
use PhilomenaWeb, :view
|
use PhilomenaWeb, :view
|
||||||
|
|
||||||
import PhilomenaWeb.LayoutView, only: [
|
import PhilomenaWeb.LayoutView,
|
||||||
stylesheet_path: 2,
|
only: [
|
||||||
dark_stylesheet_path: 1,
|
stylesheet_path: 2,
|
||||||
viewport_meta_tag: 1
|
dark_stylesheet_path: 1,
|
||||||
]
|
viewport_meta_tag: 1
|
||||||
|
]
|
||||||
|
|
||||||
@codes %{
|
@codes %{
|
||||||
400 => {"Bad Request", "Couldn't process your request!"},
|
400 => {"Bad Request", "Couldn't process your request!"},
|
||||||
403 => {"Forbidden", "Not allowed to access this page (are your cookies enabled?)"},
|
403 => {"Forbidden", "Not allowed to access this page (are your cookies enabled?)"},
|
||||||
404 => {"Not Found", "Couldn't find what you were looking for!"},
|
404 => {"Not Found", "Couldn't find what you were looking for!"},
|
||||||
500 => {"Internal Error", "Couldn't process your request!"},
|
500 => {"Internal Error", "Couldn't process your request!"}
|
||||||
}
|
}
|
||||||
|
|
||||||
# By default, Phoenix returns the status message from
|
# By default, Phoenix returns the status message from
|
||||||
|
|
|
@ -15,7 +15,7 @@ fn common_options() -> ComrakOptions {
|
||||||
options.render.hardbreaks = true;
|
options.render.hardbreaks = true;
|
||||||
options.render.github_pre_lang = true;
|
options.render.github_pre_lang = true;
|
||||||
|
|
||||||
options.extension.camoifier = Some(|s| camo::image_url(s).unwrap_or(String::from("")));
|
options.extension.camoifier = Some(|s| camo::image_url(s).unwrap_or_else(|| String::from("")));
|
||||||
|
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue