elixir-plug / plug

Compose web applications with functions
https://hex.pm/packages/plug
Other
2.84k stars 582 forks source link

Add "Copy to Markdown" button #1111

Closed dbernheisel closed 2 years ago

dbernheisel commented 2 years ago

Added the code frames to the debugger Markdown template. This is borrowed from the HTML template but reformatted for markdown. Unfortunately required some arg drilling - if you have a suggestion on avoiding that I'll be happy to rework it.

image

Add Copy to markdown button

Screen Shot 2022-07-14 at 5 00 16 PM

When clicked it changes to a success message. Reverts to original in 5 seconds.

image

Video Demo https://user-images.githubusercontent.com/643967/179085428-bf973028-748a-403c-8a0d-97c4a2b07dd4.mov

Sample Markdown output ```markdown # RuntimeError at GET / Exception: ** (RuntimeError) boom (ejecto 0.1.0) lib/ejecto_web/live/home_live.ex:6: EjectoWeb.HomeLive.mount/3 (phoenix_live_view 0.17.11) lib/phoenix_live_view/utils.ex:301: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5 (telemetry 1.1.0) /home/dbern/ejecto/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3 (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:270: Phoenix.LiveView.Static.call_mount_and_handle_params!/5 (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:110: Phoenix.LiveView.Static.render/3 (phoenix_live_view 0.17.11) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3 (phoenix 1.6.11) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2 (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.plug_builder_call/2 (ejecto 0.1.0) lib/plug/debugger.ex:136: EjectoWeb.Endpoint."call (overridable 3)"/2 (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.call/2 (phoenix 1.6.11) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4 (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2 (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3 (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3 (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3 Code: `lib/ejecto_web/live/home_live.ex` 1 defmodule EjectoWeb.HomeLive do 2 use EjectoWeb, :live_view 3 4 def mount(_params, _session, socket) do 5 if connected?(socket), do: {:ok, socket} 6> raise "boom" 7 {:ok, socket} 8 end 9 10 def render(assigns) do 11 ~H""" `lib/phoenix_live_view/utils.ex` 296 %{socket: socket, params: params, session: session, uri: uri}, 297 fn -> 298 socket = 299 case Lifecycle.mount(params, session, socket) do 300 {:cont, %Socket{} = socket} when exported? -> 301> view.mount(params, session, socket) 302 303 {_, %Socket{} = socket} -> 304 {:ok, socket} 305 end 306 |> handle_mount_result!({:mount, 3, view}) `/home/dbern/ejecto/deps/telemetry/src/telemetry.erl` 315 EventPrefix ++ [start], 316 #{monotonic_time => StartTime, system_time => erlang:system_time()}, 317 merge_ctx(StartMetadata, DefaultCtx) 318 ), 319 320> try {_, #{}} = SpanFunction() of 321 {Result, StopMetadata} -> 322 StopTime = erlang:monotonic_time(), 323 execute( 324 EventPrefix ++ [stop], 325 #{duration => StopTime - StartTime, monotonic_time => StopTime}, `lib/phoenix_live_view/static.ex` 265 266 defp call_mount_and_handle_params!(socket, view, session, params, uri) do 267 mount_params = if socket.router, do: params, else: :not_mounted_at_router 268 269 socket 270> |> Utils.maybe_call_live_view_mount!(view, mount_params, session, uri) 271 |> mount_handle_params(view, params, uri) 272 |> case do 273 {:noreply, %Socket{redirected: {:live, _, _}} = socket} -> 274 {:stop, socket} 275 `lib/phoenix_live_view/static.ex` 105 action, 106 flash, 107 host_uri 108 ) 109 110> case call_mount_and_handle_params!(socket, view, mount_session, conn.params, request_url) do 111 {:ok, socket} -> 112 data_attrs = [ 113 phx_session: sign_root_session(socket, router, view, to_sign_session, live_session), 114 phx_static: sign_static_token(socket) 115 ] `lib/phoenix_live_view/controller.ex` 34 end 35 end 36 37 """ 38 def live_render(%Plug.Conn{} = conn, view, opts \\ []) do 39> case LiveView.Static.render(conn, view, opts) do 40 {:ok, content, socket_assigns} -> 41 conn 42 |> Phoenix.Controller.put_view(LiveView.Static) 43 |> Phoenix.Controller.render( 44 "template.html", `lib/phoenix/router.ex` 349 metadata = %{metadata | conn: halted_conn} 350 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata) 351 halted_conn 352 %Plug.Conn{} = piped_conn -> 353 try do 354> plug.call(piped_conn, plug.init(opts)) 355 else 356 conn -> 357 measurements = %{duration: System.monotonic_time() - start} 358 metadata = %{metadata | conn: conn} 359 :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata) `lib/ejecto_web/endpoint.ex` 1> defmodule EjectoWeb.Endpoint do 2 use Phoenix.Endpoint, otp_app: :ejecto 3 4 # The session will be stored in the cookie and signed, 5 # this means its contents can be read but not tampered with. 6 # Set :encryption_salt if you would also like to encrypt it. `lib/plug/debugger.ex` No code available. `lib/ejecto_web/endpoint.ex` 1> defmodule EjectoWeb.Endpoint do 2 use Phoenix.Endpoint, otp_app: :ejecto 3 4 # The session will be stored in the cookie and signed, 5 # this means its contents can be read but not tampered with. 6 # Set :encryption_salt if you would also like to encrypt it. `lib/phoenix/endpoint/cowboy2_handler.ex` 49 end 50 51 {:plug, conn, handler, opts} -> 52 %{adapter: {@connection, req}} = 53 conn 54> |> handler.call(opts) 55 |> maybe_send(handler) 56 57 {:ok, req, {handler, opts}} 58 end 59 catch `/home/dbern/ejecto/deps/cowboy/src/cowboy_handler.erl` 32 -optional_callbacks([terminate/3]). 33 34 -spec execute(Req, Env) -> {ok, Req, Env} 35 when Req::cowboy_req:req(), Env::cowboy_middleware:env(). 36 execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) -> 37> try Handler:init(Req, HandlerOpts) of 38 {ok, Req2, State} -> 39 Result = terminate(normal, Req2, State, Handler), 40 {ok, Req2, Env#{result => Result}}; 41 {Mod, Req2, State} -> 42 Mod:upgrade(Req2, Env, Handler, State); `/home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl` 301 end. 302 303 execute(_, _, []) -> 304 ok; 305 execute(Req, Env, [Middleware|Tail]) -> 306> case Middleware:execute(Req, Env) of 307 {ok, Req2, Env2} -> 308 execute(Req2, Env2, Tail); 309 {suspend, Module, Function, Args} -> 310 proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]); 311 {stop, _Req2} -> `/home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl` 290 %% to simplify the debugging of errors. The proc_lib library 291 %% already adds the stacktrace to other types of exceptions. 292 -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok. 293 request_process(Req, Env, Middlewares) -> 294 try 295> execute(Req, Env, Middlewares) 296 catch 297 exit:Reason={shutdown, _}:Stacktrace -> 298 erlang:raise(exit, Reason, Stacktrace); 299 exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown -> 300 erlang:raise(exit, {Reason, Stacktrace}, Stacktrace) `proc_lib.erl` No code available. ## Connection details ### Params %{} ### Request info * URI: http://192.168.86.59:4000/ * Query string: ### Headers * accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 * accept-encoding: gzip, deflate * accept-language: en-US,en;q=0.9 * cache-control: max-age=0 * connection: keep-alive * cookie: _ejecto_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYc0d6MDl0czdUaUZWUHJKQjlNRWJONjJT.sKzUbyldKTGwL1CuSMjr7PWMzsVXjw6Jh13luyPzK-c * dnt: 1 * host: 192.168.86.59:4000 * referer: http://192.168.86.59:4000/ * upgrade-insecure-requests: 1 * user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 ### Session %{"_csrf_token" => "sGz09ts7TiFVPrJB9MEbN62S"} ```
Example of bad match ```markdown # FunctionClauseError at GET / Exception: ** (FunctionClauseError) no function clause matching in EjectoWeb.HomeLive.boom/2 (ejecto 0.1.0) lib/ejecto_web/live/home_live.ex:10: EjectoWeb.HomeLive.boom(:fail, :foo) (phoenix_live_view 0.17.11) lib/phoenix_live_view/utils.ex:301: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5 (telemetry 1.1.0) /home/dbern/ejecto/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3 (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:270: Phoenix.LiveView.Static.call_mount_and_handle_params!/5 (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:110: Phoenix.LiveView.Static.render/3 (phoenix_live_view 0.17.11) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3 (phoenix 1.6.11) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2 (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.plug_builder_call/2 (ejecto 0.1.0) lib/plug/debugger.ex:136: EjectoWeb.Endpoint."call (overridable 3)"/2 (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.call/2 (phoenix 1.6.11) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4 (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2 (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3 (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3 (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3 Code: `lib/ejecto_web/live/home_live.ex` 5 if connected?(socket), do: {:ok, socket} 6 boom(:fail, :foo) 7 {:ok, socket} 8 end 9 10> defp boom(nil, :foo), do: :wat 11 defp boom(1, :foo), do: :wat 12 defp boom("a", :foo), do: :wat 13 defp boom([], :foo), do: :wat 14 15 def render(assigns) do Called with 2 arguments * `:fail` * `:foo` Attempted function clauses (showing 4 out of 4) defp boom(nil, :foo) defp boom(1, :foo) defp boom("a", :foo) defp boom([], :foo) `lib/phoenix_live_view/utils.ex` ... ```
dbernheisel commented 2 years ago

I just realized I used heroicons which are MIT licensed. Either we need to include their license or replace with license-free icons or remove the icons.

josevalim commented 2 years ago

It looks great @dbernheisel, thank you!

Regarding the button, I think you can remove the border and background around the button, and keep it as a text as in "Show only app frames". The text can be in regular text color or the primary color (as you did), your call.

About the icon, we can go with UXWing, such as: https://uxwing.com/copy-icon/

dbernheisel commented 2 years ago

Updated the icon and style. I also aligned the checkbox label and input. Screenshot 2022-07-16 10 38 51 AM Screenshot 2022-07-16 10 39 01 AM

josevalim commented 2 years ago

Thank you! One last nitpick: can you please vertically align the text with "Show only app frames" and make it the same font color? That's what I meant by "regular text color" but apparently "Show only app frames" is not regular text color. Sorry about that.

dbernheisel commented 2 years ago

adjusted the style. Good calls, np about the nitpicks as surely someone would come after me and fix these little visual annoyances :) and happy to get them now.

image image
josevalim commented 2 years ago

:green_heart: :blue_heart: :purple_heart: :yellow_heart: :heart: