Skip to content

Instantly share code, notes, and snippets.

@luylucas10
Last active March 22, 2018 01:28
Show Gist options
  • Save luylucas10/2970d92f0677374f56cf6825d375040a to your computer and use it in GitHub Desktop.
Save luylucas10/2970d92f0677374f56cf6825d375040a to your computer and use it in GitHub Desktop.
Menu Dinâmico
<aside class="control-sidebar control-sidebar-dark">
<!-- Create the tabs -->
<ul class="nav nav-tabs nav-justified control-sidebar-tabs">
<li class="active">
<a href="#control-sidebar-home-tab" data-toggle="tab">
<i class="fa fa-home"></i>
</a>
</li>
<li>
<a href="#control-sidebar-settings-tab" data-toggle="tab">
<i class="fa fa-gears"></i>
</a>
</li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<!-- Home tab content -->
<div class="tab-pane active" id="control-sidebar-home-tab">
<h3 class="control-sidebar-heading">Recent Activity</h3>
<ul class="control-sidebar-menu">
<li>
<a href="javascript:;">
<i class="menu-icon fa fa-birthday-cake bg-red"></i>
<div class="menu-info">
<h4 class="control-sidebar-subheading">Langdon's Birthday</h4>
<p>Will be 23 on April 24th</p>
</div>
</a>
</li>
</ul>
<!-- /.control-sidebar-menu -->
<h3 class="control-sidebar-heading">Tasks Progress</h3>
<ul class="control-sidebar-menu">
<li>
<a href="javascript:;">
<h4 class="control-sidebar-subheading">
Custom Template Design
<span class="pull-right-container">
<span class="label label-danger pull-right">70%</span>
</span>
</h4>
<div class="progress progress-xxs">
<div class="progress-bar progress-bar-danger" style="width: 70%"></div>
</div>
</a>
</li>
</ul>
<!-- /.control-sidebar-menu -->
</div>
<!-- /.tab-pane -->
<!-- Stats tab content -->
<div class="tab-pane" id="control-sidebar-stats-tab">Stats Tab Content</div>
<!-- /.tab-pane -->
<!-- Settings tab content -->
<div class="tab-pane" id="control-sidebar-settings-tab">
<form method="post">
<h3 class="control-sidebar-heading">General Settings</h3>
<div class="form-group">
<label class="control-sidebar-subheading">
Report panel usage
<input type="checkbox" class="pull-right" checked>
</label>
<p>
Some information about this general settings option
</p>
</div>
<!-- /.form-group -->
</form>
</div>
<!-- /.tab-pane -->
</div>
</aside>
<div class="control-sidebar-bg"></div>
@{
Layout = null;
}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>@ViewBag.Title - Meu .NET</title>
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
<!-- Bootstrap -->
<link href="~/Content/bootstrap.min.css" rel="stylesheet" />
<!-- Font Awesome -->
<link href="~/Content/font-awesome.min.css" rel="stylesheet" />
<!-- AdminLTE -->
<link href="~/Content/adminlte/css/AdminLTE.min.css" rel="stylesheet" />
<link href="~/Content/adminlte/css/skins/skin-blue.min.css" rel="stylesheet" />
@RenderSection("styles", false)
</head>
<body class="skin-blue sidebar-mini sidebar-collapse" style="height: auto; min-height: 100%;">
<div class="wrapper" style="height: auto; min-height: 100%;">
<!-- Topbar-->
@Html.Partial("_Topbar")
<!-- Sidebar-->
<div id="sidebar" data-url="@Url.Action("Sidebar", "Menu")"></div>
<!-- Content -->
<div class="content-wrapper" style="min-height: 536px;">
<section class="content container-fluid">
@RenderBody()
</section>
</div>
<!-- Footer-->
@Html.Partial("_Footer")
</div>
<!-- jQuery-->
<script src="~/Scripts/jquery-3.3.1.min.js"></script>
<!-- Boostrap -->
<script src="~/Scripts/bootstrap.min.js"></script>
<!-- AdminLTE -->
<script src="~/Content/adminlte/js/adminlte.min.js"></script>
<!-- Último passo para carregar o menu de forma dinâmica -->
<script>
$("#sidebar").load($("#sidebar").data("url"),
function () {
$("ul[data-widget='tree']").tree();
});
</script>
@RenderSection("scripts", false)
</body>
@using Meudotnet.MenuDinamico.Mvc.Helpers
@model IList<Meudotnet.MenuDinamico.Mvc.ViewModels.MenuViewModel>
<aside class="main-sidebar">
<section class="sidebar">
<ul class="sidebar-menu tree" data-widget="tree">
<li class="header">Menu</li>
@Html.Sidebar(Model, Url)
</ul>
</section>
</aside>
<header class="main-header">
<a class="logo">
<span class="logo-mini">
<img src="/Content/img/logo-25x25.png" alt="" />
</span>
<span class="logo-lg">
<img src="/Content/img/logo-25x25.png" alt="" />
@{
string title = "Meu .NET MVC";
}
@title
</span>
</a>
<nav class="navbar navbar-static-top" role="navigation">
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<li class="dropdown user user-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span class="hidden-xs">Usuário</span>
</a>
<ul class="dropdown-menu" style="width: 100px !important;">
@*<li class="user-footer">
<div class="pull-left">
<a href="@Url.Action("Index", "Manage")" class="btn btn-default btn-flat">Conta</a>
</div>
<div class="pull-right">
@using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
{
@Html.AntiForgeryToken()
<a href="javascript:document.getElementById('logoutForm').submit()" class="btn btn-default btn-flat">Sair</a>
}
</div>
</li>*@
</ul>
</li>
</ul>
</div>
</nav>
</header>
@model Meudotnet.MenuDinamico.Mvc.ViewModels.MenuViewModel
@{
ViewBag.Title = "Cadastrar Menu";
}
@section styles
{
<link href="~/Content/select2-4.0.5/dist/css/select2.css" rel="stylesheet" />
<link href="~/Content/adminlte/css/alt/AdminLTE-select2.css" rel="stylesheet" />
}
@using (Html.BeginForm("Create", "Menu", FormMethod.Post))
{
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">Novo Menu</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form class="form-horizontal">
<div class="box-body">
<div class="form-group">
@Html.LabelFor(x => x.RootId)
@Html.DropDownListFor(x => x.RootId, new List<SelectListItem>(), new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(x => x.Name)
@Html.TextBoxFor(x => x.Name, new { @class = "form-control" })
</div>
<div class="form-group">
@Html.LabelFor(x => x.Order)
@Html.TextBoxFor(x => x.Order, new { @class = "form-control", type = "number" })
</div>
<div class="form-group">
@Html.LabelFor(x => x.Level)
@Html.TextBoxFor(x => x.Level, new { @class = "form-control", type = "number" })
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-info pull-right">
<span class="fa fa-save"></span>
</button>
</div>
<!-- /.box-footer -->
</form>
</div>
}
@section scripts
{
<script src="~/Content/select2-4.0.5/dist/js/select2.js"></script>
<script src="~/Content/select2-4.0.5/dist/js/i18n/pt-BR.js"></script>
<script>
$(function () {
$("select").select2({
placeholder: "Selecione menu pai",
language: "pt-BR",
width: "100%",
allowClear: true,
ajax: {
url: "/Menu/Search",
type: "GET",
delay: 400,
data: function (params) {
var query = {
name: params.term,
page: params.page || 1
}
return query;
},
processResults: function (data, params) {
params.page = params.page || 1;
return {
results: $.map(data.Result,
function (item, index) {
var k = { id: item.Id, text: item.Name };
return k;
}),
pagination: {
more: (params.page * 15) < data.Total
}
};
}
}
});
});
</script>
}
@model Meudotnet.MenuDinamico.Mvc.ViewModels.MenuViewModel
@{
ViewBag.Title = "Delete";
}
@using (Html.BeginForm("ConfirmDelete", "Menu", FormMethod.Post))
{
<div class="box box-danger">
<div class="box-header">
<h5 class="box-title">Delete Menu</h5>
</div>
<div class="box-body">
@Html.HiddenFor(x => x.Id)
<div class="form-group">
@Html.LabelFor(x => x.Name)<br/>
@Html.DisplayTextFor(x => x.Name)
</div>
<button class="btn btn-danger">
<span class="fa fa-trash"></span>
</button>
</div>
</div>
}
@model Meudotnet.MenuDinamico.Mvc.ViewModels.MenuViewModel
@{
ViewBag.Title = "Cadastrar Menu";
}
@section styles
{
<link href="~/Content/select2-4.0.5/dist/css/select2.css" rel="stylesheet" />
<link href="~/Content/adminlte/css/alt/AdminLTE-select2.css" rel="stylesheet" />
}
@using (Html.BeginForm("Edit", "Menu", FormMethod.Post))
{
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">Editar Menu</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form class="form-horizontal">
<div class="box-body">
@Html.HiddenFor(x => x.Id)
<div class="form-group">
@Html.LabelFor(x => x.RootId)
@Html.DropDownListFor(x => x.RootId, new List<SelectListItem>(), new {@class = "form-control", data_value=Model.RootId})
</div>
<div class="form-group">
@Html.LabelFor(x => x.Name)
@Html.TextBoxFor(x => x.Name, new {@class = "form-control"})
</div>
<div class="form-group">
@Html.LabelFor(x => x.Order)
@Html.TextBoxFor(x => x.Order, new {@class = "form-control", type = "number"})
</div>
<div class="form-group">
@Html.LabelFor(x => x.Level)
@Html.TextBoxFor(x => x.Level, new {@class = "form-control", type = "number"})
</div>
</div>
<!-- /.box-body -->
<div class="box-footer">
<button type="submit" class="btn btn-info pull-right">
<span class="fa fa-save"></span>
</button>
</div>
<!-- /.box-footer -->
</form>
</div>
}
@section scripts
{
<script src="~/Content/select2-4.0.5/dist/js/select2.js"></script>
<script src="~/Content/select2-4.0.5/dist/js/i18n/pt-BR.js"></script>
<script>
$(function () {
var select = $("#RootId");
select.select2({
placeholder: "Selecione menu pai",
language: "pt-BR",
width: "100%",
allowClear: true,
ajax: {
url: "/Menu/Search",
type: "GET",
delay: 400,
data: function (params) {
var query = {
name: params.term,
page: params.page || 1
}
return query;
},
processResults: function (data, params) {
params.page = params.page || 1;
return {
results: $.map(data.Result,
function (item, index) {
var k = { id: item.Id, text: item.Name };
return k;
}),
pagination: {
more: (params.page * 15) < data.Total
}
};
}
}
});
var paiId = select.data("value");
if (paiId) {
var paiOpt = $("<option selected>Carregando...</option>").val(paiId);
select.append(paiOpt).trigger("change");
$.ajax({
type: "GET",
url: "/Menu/Search",
data: { id: paiId },
dataType: "json"
}).then(function (data) {
paiOpt.text(data.Result[0].Name).val(data.Result[0].Id);
paiOpt.removeData();
select.trigger("change");
});
}
});
</script>
}
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using Meudotnet.MenuDinamico.Mvc.ViewModels;
namespace Meudotnet.MenuDinamico.Mvc.Helpers
{
public static class HtmlExtensions
{
public static IHtmlString Sidebar(this HtmlHelper helper, IList<MenuViewModel> menus, UrlHelper url)
{
var html = new StringBuilder();
BuildTags(menus, ref html, 0, url);
var ht = html.ToString();
return new MvcHtmlString(ht);
}
private static void BuildTags(IList<MenuViewModel> list, ref StringBuilder html, int index, UrlHelper helper)
{
while (true)
{
if (index < list.Count)
{
if (list[index].Menus != null && list[index].Menus.Any())
{
html.AppendLine($@" <li class='treeview'>");
html.AppendLine($@" <a href='#'>");
html.AppendLine($@" <i class='{list[index].Icon}'></i>");
html.AppendLine($@" <span>{list[index].Name}</span>");
html.AppendLine($@" <span class='pull-right-container'>");
html.AppendLine($@" <i class='fa fa-angle-left pull-right'></i>");
html.AppendLine($@" </span>");
html.AppendLine($@" </a>");
html.AppendLine($@" <ul class='treeview-menu'>");
BuildTags((List<MenuViewModel>) list[index].Menus, ref html, 0, helper);
html.AppendLine($@" </ul>");
html.AppendLine($@" </li>");
}
else
{
html.AppendLine($@" <li>");
html.AppendLine($@" <a href='{helper.Action(list[index].Action, list[index].Controller)}'>");
html.AppendLine($@" <i class='{list[index].Icon}'></i>");
html.AppendLine($@" <span>{list[index].Name}</span>");
html.AppendLine($@" </a>");
html.AppendLine($@" </li>");
}
index = index + 1;
continue;
}
break;
}
}
}
}
@{
ViewBag.Title = "Menus";
var url = (string)ViewBag.Url;
}
@section styles
{
<link href="~/Content/bootstrap-table/dist/bootstrap-table.css" rel="stylesheet" />
}
<div class="panel panel-info">
<div class="panel-heading clearfix">
<div class="pull-right">
<a class="btn btn-primary" href="#">
<span class="fa fa-plus"></span>
</a>
</div>
<h4 class="panel-title">Menus Cadastrados</h4>
</div>
<div class="panel-body">
@Html.Hidden("Url", url)
<table></table>
</div>
</div>
@section scripts
{
<script src="~/Content/bootstrap-table/dist/bootstrap-table.js"></script>
<script src="~/Content/bootstrap-table/dist/locale/bootstrap-table-pt-BR.js"></script>
<script src="~/Scripts/menu/index.js"></script>
}
function rowActions(value, row, index) {
return [
"<div class='pull-right'>",
"<a href='/Menu/Edit/"+row.Id+"' class='btn btn-sm btn-primary' title='Atualizar Registro'><span class='fa fa-pencil'></span></a>",
"<a href='/Menu/Delete/"+row.Id+"' class='btn btn-sm btn-danger' title='Excluir Registro'><span class='fa fa-trash'></span></a>",
"</div>"].join("\n");
};
$(function () {
$("table").bootstrapTable({
url: $("#Url").val(),
queryParamsType: "",
queryParams: function (params) {
var query = {
page: params.pageNumber,
pageSize: params.pageSize,
orderby: params.sortName,
sort: params.sortOrder,
name: params.searchText
};
return query;
},
columns: [
{ field: "Id", visible: false },
{ field: "Name", title: "Menu", sortable: true, halign: "center" },
{ field: "Level", title: "Nível", halign: "center" },
{ title: "", formatter: rowActions, width: "100px" }
],
search: true,
searchText: "",
searchOnEnterKey: true,
classes: "table table-responsive table-hover",
idField: "Id",
locale: "pt-BR",
method: "GET",
cache: false,
dataField: "Result",
totalField: "Total",
pagination: true,
paginationLoop: false,
sidePagination: "server",
pageNumber: 1,
pageSize: 15,
pageList: [15, 30, 45, 60],
});
});
USE ['Sua Base']
GO
WITH menus (MenuId, MenuRootId, [Name], [Level], [Order], Controller, [Action], Icon, Css) AS (
SELECT m.MenuId, m.MenuRootId, m.[Name], 1, cast(m.[Order] as varchar(50)) as OrderS, m.Controller, m.Action, m.Icon, m.Css
FROM Menu m WHERE m.MenuRootId IS NULL
UNION ALL
SELECT m.MenuId, m.MenuRootId, m.[Name], m2.[Level]+1, cast(cast(m2.[Order] AS varchar(10)) + '.' + cast(m.[Order] AS varchar(10)) as varchar(50)) as OrdemS, m.Controller, m.Action, m.Icon, m.Css
FROM Menu m INNER JOIN menus m2 ON m.MenuRootId = m2.MenuId)
SELECT MenuId, MenuRootId, [Name], [Level], [Order] AS OrdemS, m.Controller, [Action], m.Icon, m.Css FROM menus m order by [Order]
USE ['Sua Base']
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Menu](
[MenuId] [int] IDENTITY(1,1) NOT NULL,
[MenuRootId] [int] NULL,
[Name] [varchar](30) NOT NULL,
[Level] [int] NOT NULL,
[Order] [int] NOT NULL,
[Controller] [varchar](100) NULL,
[Action] [varchar](100) NULL,
[Icon] [varchar](100) NULL,
[Css] [varchar](100) NULL,
CONSTRAINT [PK_Menu] PRIMARY KEY CLUSTERED ([MenuId] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Menu] WITH CHECK ADD CONSTRAINT [FK_Menu_Menu] FOREIGN KEY([MenuRootId])
REFERENCES [dbo].[Menu] ([MenuId])
GO
ALTER TABLE [dbo].[Menu] CHECK CONSTRAINT [FK_Menu_Menu]
GO
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Dapper;
using Meudotnet.MenuDinamico.Mvc.Filters;
using Meudotnet.MenuDinamico.Mvc.ViewModels;
namespace Meudotnet.MenuDinamico.Mvc.Controllers
{
public class MenuController : Controller
{
public ActionResult Index()
{
ViewBag.Url = Url.Action("Search");
return View();
}
public async Task<ActionResult> Search(MenuFilter filter)
{
var query = new StringBuilder();
query.Append($@"
WITH menus (MenuId, MenuRootId, [Name], [Level], [Order], Controller, [Action], Icon, Css) AS
(
SELECT m.MenuId, m.MenuRootId, cast(m.[Name] as varchar(max)), 1, cast(m.[Order] as varchar(50)) as OrderS, m.Controller, m.Action, m.Icon, m.Css
FROM Menu m
WHERE m.MenuRootId IS NULL
UNION ALL
SELECT m.MenuId, m.MenuRootId, m2.[Name] +'/'+ m.[Name] as [Name], m2.[Level]+1, cast(cast(m2.[Order] AS varchar(10)) + '.' + cast(m.[Order] AS varchar(10)) as varchar(50)) as OrdemS, m.Controller, m.Action, m.Icon, m.Css
FROM Menu m
INNER JOIN menus m2 ON m.MenuRootId = m2.MenuId
)");
query.AppendLine(
$@"SELECT MenuId, MenuRootId, [Name], [Level], [Order] AS OrdemS, m.Controller, [Action], m.Icon, m.Css, COUNT(*) OVER() as Total FROM menus m ");
if (!string.IsNullOrEmpty(filter.Name))
{
query.AppendLine($@"WHERE m.Name LIKE '%{string.Join("%",filter.Name.Split())}%' ");
}
if (filter.PageSize < 0)
filter.PageSize = 15;
if (filter.Page <= 1)
filter.Page = 0;
else
filter.Page = (filter.Page - 1) * filter.PageSize;
query.AppendLine($"ORDER BY {filter.OrderBy ?? "Name"} {filter.Sort ?? "ASC"} ");
query.AppendLine($"OFFSET {filter.Page} ROWS FETCH NEXT {filter.PageSize} ROWS ONLY");
var cs = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
using (var cn = new SqlConnection(cs))
{
var list = await cn.QueryAsync<MenuViewModel>(query.ToString());
var menuViewModels = list as MenuViewModel[] ?? list.ToArray();
return Json(new {Result = menuViewModels, Total = menuViewModels.FirstOrDefault()?.Total ?? 0}, JsonRequestBehavior.AllowGet);
}
}
//public ActionResult Create()
//{
// return PartialView("_Create");
//}
//public ActionResult Create(MenuViewModel viewModel)
//{
//}
[HttpGet]
public async Task<ActionResult> Sidebar()
{
var cs = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
using (var cn = new SqlConnection(cs))
{
var enumerable = await cn.QueryAsync<MenuViewModel>($@"
WITH menus (MenuId, MenuRootId, [Name], [Level], [Order], Controller, [Action], Icon, Css) AS
(
SELECT m.MenuId, m.MenuRootId, m.[Name], 1, cast(m.[Order] as varchar(50)) as OrderS, m.Controller, m.Action, m.Icon, m.Css
FROM Menu m
WHERE m.MenuRootId IS NULL
UNION ALL
SELECT m.MenuId, m.MenuRootId, m.[Name], m2.[Level]+1, cast(cast(m2.[Order] AS varchar(10)) + '.' + cast(m.[Order] AS varchar(10)) as varchar(50)) as OrdemS, m.Controller, m.Action, m.Icon, m.Css
FROM Menu m
INNER JOIN menus m2 ON m.MenuRootId = m2.MenuId
)
SELECT MenuId, MenuRootId, [Name], [Level], [Order] AS OrdemS, m.Controller, [Action], m.Icon, m.Css FROM menus m order by [Order]");
IList<MenuViewModel> list = enumerable.ToList();
BuildTree(ref list, 0, 1);
return PartialView("_Sidebar",list);
}
}
/// <summary>
/// Este cara constrói a árvore, dada a lista;
/// Ele é recursivo, para que possa acessar vários níveis;
/// </summary>
/// <param name="list"></param>
/// <param name="parent"></param>
/// <param name="next"></param>
private void BuildTree(ref IList<MenuViewModel> list, int parent, int next)
{
while (true)
{
if (next < list.Count)
{
if (list[parent].Level < list[next].Level)
{
parent = next;
next++;
}
else if (list[parent].Level > list[next].Level)
{
parent--;
next--;
list[parent].Menus.Add(list[next]);
list.RemoveAt(next);
next--;
}
else if (list[parent].Level == list[next].Level)
{
if (parent - 1 >= 0)
if (list[parent - 1].Level == list[parent].Level)
{
parent++;
next = parent + 1;
}
else
{
parent--;
list[parent].Menus.Add(list[next]);
list.RemoveAt(next);
next--;
}
else
next++;
}
continue;
}
else
{
// neste ponto é para que os menus abaixo sejam adicionados como filhos do último menu;
// ele é necessário, por mais que não faça sentido, tire e entenda o porquê;
for (var i = list.Count - 1; i >= parent - 1; i--)
if (i <= 0)
break;
else if (list[i].Level > list[i - 1].Level)
{
list[i - 1].Menus.Add(list[i]);
list.RemoveAt(i);
}
}
break;
}
}
}
}
namespace Meudotnet.MenuDinamico.Mvc.Filters
{
public class MenuFilter
{
public int Page { get; set; }
public int PageSize { get; set; }
public string OrderBy { get; set; }
public string Sort { get; set; }
public string Name { get; set; }
public int? Id { get; set; }
}
}
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Meudotnet.MenuDinamico.Mvc.ViewModels
{
public class MenuViewModel
{
[Key]
public int Id { get; set; }
public int? RootId { get; set; }
[Required]
public string Name { get; set; }
public int? Level { get; set; }
public int? Order { get; set; }
public string Controller { get; set; }
public string Action { get; set; }
public string Icon { get; set; }
public string Css { get; set; }
public IList<MenuViewModel> Menus { get; set; }
public MenuViewModel()
{
Menus = new List<MenuViewModel>();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment