diff --git a/cmd/root.go b/cmd/root.go index a6803572127..519c2e1af51 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,9 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/internal/gopath" "github.com/urfave/cli" + + // Required since otherwise dep will prune away these unused packages before codegen has a chance to run + _ "github.com/99designs/gqlgen/handler" ) func Execute() { diff --git a/codegen/templates/data.go b/codegen/templates/data.go index 832b74c60f5..d3098aaae14 100644 --- a/codegen/templates/data.go +++ b/codegen/templates/data.go @@ -8,6 +8,6 @@ var data = map[string]string{ "interface.gotpl": "{{- $interface := . }}\n\nfunc (ec *executionContext) _{{$interface.GQLType}}(ctx context.Context, sel ast.SelectionSet, obj *{{$interface.FullName}}) graphql.Marshaler {\n\tswitch obj := (*obj).(type) {\n\tcase nil:\n\t\treturn graphql.Null\n\t{{- range $implementor := $interface.Implementors }}\n\t\t{{- if $implementor.ValueReceiver }}\n\t\t\tcase {{$implementor.FullName}}:\n\t\t\t\treturn ec._{{$implementor.GQLType}}(ctx, sel, &obj)\n\t\t{{- end}}\n\t\tcase *{{$implementor.FullName}}:\n\t\t\treturn ec._{{$implementor.GQLType}}(ctx, sel, obj)\n\t{{- end }}\n\tdefault:\n\t\tpanic(fmt.Errorf(\"unexpected type %T\", obj))\n\t}\n}\n", "models.gotpl": "// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\npackage {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\n{{ range $model := .Models }}\n\t{{with .Description}} {{.|prefixLines \"// \"}} {{end}}\n\t{{- if .IsInterface }}\n\t\ttype {{.GoType}} interface {\n\t\t\tIs{{.GoType}}()\n\t\t}\n\t{{- else }}\n\t\ttype {{.GoType}} struct {\n\t\t\t{{- range $field := .Fields }}\n\t\t\t\t{{- with .Description}}\n\t\t\t\t\t{{.|prefixLines \"// \"}}\n\t\t\t\t{{- end}}\n\t\t\t\t{{- if $field.GoFieldName }}\n\t\t\t\t\t{{ $field.GoFieldName }} {{$field.Signature}} `json:\"{{$field.GQLName}}\"`\n\t\t\t\t{{- else }}\n\t\t\t\t\t{{ $field.GoFKName }} {{$field.GoFKType}}\n\t\t\t\t{{- end }}\n\t\t\t{{- end }}\n\t\t}\n\n\t\t{{- range $iface := .Implements }}\n\t\t\tfunc ({{$model.GoType}}) Is{{$iface.GoType}}() {}\n\t\t{{- end }}\n\n\t{{- end }}\n{{- end}}\n\n{{ range $enum := .Enums }}\n\t{{with .Description}}{{.|prefixLines \"// \"}} {{end}}\n\ttype {{.GoType}} string\n\tconst (\n\t{{- range $value := .Values}}\n\t\t{{- with .Description}}\n\t\t\t{{.|prefixLines \"// \"}}\n\t\t{{- end}}\n\t\t{{$enum.GoType}}{{ .Name|toCamel }} {{$enum.GoType}} = {{.Name|quote}}\n\t{{- end }}\n\t)\n\n\tfunc (e {{.GoType}}) IsValid() bool {\n\t\tswitch e {\n\t\tcase {{ range $index, $element := .Values}}{{if $index}},{{end}}{{ $enum.GoType }}{{ $element.Name|toCamel }}{{end}}:\n\t\t\treturn true\n\t\t}\n\t\treturn false\n\t}\n\n\tfunc (e {{.GoType}}) String() string {\n\t\treturn string(e)\n\t}\n\n\tfunc (e *{{.GoType}}) UnmarshalGQL(v interface{}) error {\n\t\tstr, ok := v.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"enums must be strings\")\n\t\t}\n\n\t\t*e = {{.GoType}}(str)\n\t\tif !e.IsValid() {\n\t\t\treturn fmt.Errorf(\"%s is not a valid {{.GQLType}}\", str)\n\t\t}\n\t\treturn nil\n\t}\n\n\tfunc (e {{.GoType}}) MarshalGQL(w io.Writer) {\n\t\tfmt.Fprint(w, strconv.Quote(e.String()))\n\t}\n\n{{- end }}\n", "object.gotpl": "{{ $object := . }}\n\nvar {{ $object.GQLType|lcFirst}}Implementors = {{$object.Implementors}}\n\n// nolint: gocyclo, errcheck, gas, goconst\n{{- if .Stream }}\nfunc (ec *executionContext) _{{$object.GQLType}}(ctx context.Context, sel ast.SelectionSet) func() graphql.Marshaler {\n\tfields := graphql.CollectFields(ctx, sel, {{$object.GQLType|lcFirst}}Implementors)\n\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\tObject: {{$object.GQLType|quote}},\n\t})\n\tif len(fields) != 1 {\n\t\tec.Errorf(ctx, \"must subscribe to exactly one stream\")\n\t\treturn nil\n\t}\n\n\tswitch fields[0].Name {\n\t{{- range $field := $object.Fields }}\n\tcase \"{{$field.GQLName}}\":\n\t\treturn ec._{{$object.GQLType}}_{{$field.GQLName}}(ctx, fields[0])\n\t{{- end }}\n\tdefault:\n\t\tpanic(\"unknown field \" + strconv.Quote(fields[0].Name))\n\t}\n}\n{{- else }}\nfunc (ec *executionContext) _{{$object.GQLType}}(ctx context.Context, sel ast.SelectionSet{{if not $object.Root}}, obj *{{$object.FullName}} {{end}}) graphql.Marshaler {\n\tfields := graphql.CollectFields(ctx, sel, {{$object.GQLType|lcFirst}}Implementors)\n\t{{if $object.Root}}\n\t\tctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{\n\t\t\tObject: {{$object.GQLType|quote}},\n\t\t})\n\t{{end}}\n\n\t{{if $object.IsConcurrent}} var wg sync.WaitGroup {{end}}\n\tout := graphql.NewOrderedMap(len(fields))\n\tinvalid := false\n\tfor i, field := range fields {\n\t\tout.Keys[i] = field.Alias\n\n\t\tswitch field.Name {\n\t\tcase \"__typename\":\n\t\t\tout.Values[i] = graphql.MarshalString({{$object.GQLType|quote}})\n\t\t{{- range $field := $object.Fields }}\n\t\tcase \"{{$field.GQLName}}\":\n\t\t\t{{- if $field.IsConcurrent }}\n\t\t\t\twg.Add(1)\n\t\t\t\tgo func(i int, field graphql.CollectedField) {\n\t\t\t{{- end }}\n\t\t\t\tout.Values[i] = ec._{{$object.GQLType}}_{{$field.GQLName}}(ctx, field{{if not $object.Root}}, obj{{end}})\n\t\t\t\t{{- if $field.ASTType.NonNull }}\n\t\t\t\t\tif out.Values[i] == graphql.Null {\n\t\t\t\t\t\tinvalid = true\n\t\t\t\t\t}\n\t\t\t\t{{- end }}\n\t\t\t{{- if $field.IsConcurrent }}\n\t\t\t\t\twg.Done()\n\t\t\t\t}(i, field)\n\t\t\t{{- end }}\n\t\t{{- end }}\n\t\tdefault:\n\t\t\tpanic(\"unknown field \" + strconv.Quote(field.Name))\n\t\t}\n\t}\n\t{{if $object.IsConcurrent}} wg.Wait() {{end}}\n\tif invalid { return graphql.Null }\n\treturn out\n}\n{{- end }}\n", - "resolver.gotpl": "//go:generate gorunpkg github.com/99designs/gqlgen\n\npackage {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/99designs/gqlgen/handler\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\ntype {{.ResolverType}} struct {}\n\n{{ range $object := .Objects -}}\n\t{{- if $object.HasResolvers -}}\n\t\tfunc (r *{{$.ResolverType}}) {{$object.GQLType}}() {{ $object.ResolverInterface.FullName }} {\n\t\t\treturn &{{lcFirst $object.GQLType}}Resolver{r}\n\t\t}\n\t{{ end -}}\n{{ end }}\n\n{{ range $object := .Objects -}}\n\t{{- if $object.HasResolvers -}}\n\t\ttype {{lcFirst $object.GQLType}}Resolver struct { *Resolver }\n\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{- if $field.IsResolver -}}\n\t\t\tfunc (r *{{lcFirst $object.GQLType}}Resolver) {{ $field.ShortResolverDeclaration }} {\n\t\t\t\tpanic(\"not implemented\")\n\t\t\t}\n\t\t\t{{ end -}}\n\t\t{{ end -}}\n\t{{ end -}}\n{{ end }}\n", + "resolver.gotpl": "package {{ .PackageName }}\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"fmt\" }}\n\t{{ reserveImport \"io\" }}\n\t{{ reserveImport \"strconv\" }}\n\t{{ reserveImport \"time\" }}\n\t{{ reserveImport \"sync\" }}\n\t{{ reserveImport \"errors\" }}\n\t{{ reserveImport \"bytes\" }}\n\n\t{{ reserveImport \"github.com/99designs/gqlgen/handler\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser\" }}\n\t{{ reserveImport \"github.com/vektah/gqlparser/ast\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/graphql/introspection\" }}\n)\n\ntype {{.ResolverType}} struct {}\n\n{{ range $object := .Objects -}}\n\t{{- if $object.HasResolvers -}}\n\t\tfunc (r *{{$.ResolverType}}) {{$object.GQLType}}() {{ $object.ResolverInterface.FullName }} {\n\t\t\treturn &{{lcFirst $object.GQLType}}Resolver{r}\n\t\t}\n\t{{ end -}}\n{{ end }}\n\n{{ range $object := .Objects -}}\n\t{{- if $object.HasResolvers -}}\n\t\ttype {{lcFirst $object.GQLType}}Resolver struct { *Resolver }\n\n\t\t{{ range $field := $object.Fields -}}\n\t\t\t{{- if $field.IsResolver -}}\n\t\t\tfunc (r *{{lcFirst $object.GQLType}}Resolver) {{ $field.ShortResolverDeclaration }} {\n\t\t\t\tpanic(\"not implemented\")\n\t\t\t}\n\t\t\t{{ end -}}\n\t\t{{ end -}}\n\t{{ end -}}\n{{ end }}\n", "server.gotpl": "package main\n\nimport (\n\t%%%IMPORTS%%%\n\n\t{{ reserveImport \"context\" }}\n\t{{ reserveImport \"log\" }}\n\t{{ reserveImport \"net/http\" }}\n\t{{ reserveImport \"os\" }}\n\t{{ reserveImport \"github.com/99designs/gqlgen/handler\" }}\n)\n\nconst defaultPort = \"8080\"\n\nfunc main() {\n\tport := os.Getenv(\"PORT\")\n\tif port == \"\" {\n\t\tport = defaultPort\n\t}\n\n\thttp.Handle(\"/\", handler.Playground(\"GraphQL playground\", \"/query\"))\n\thttp.Handle(\"/query\", handler.GraphQL({{ lookupImport .ExecPackageName }}.NewExecutableSchema({{ lookupImport .ExecPackageName}}.Config{Resolvers: &{{ lookupImport .ResolverPackageName}}.Resolver{}})))\n\n\tlog.Printf(\"connect to http://localhost:%s/ for GraphQL playground\", port)\n\tlog.Fatal(http.ListenAndServe(\":\" + port, nil))\n}\n", } diff --git a/codegen/templates/resolver.gotpl b/codegen/templates/resolver.gotpl index 8628506c866..53ba8c43380 100644 --- a/codegen/templates/resolver.gotpl +++ b/codegen/templates/resolver.gotpl @@ -1,5 +1,3 @@ -//go:generate gorunpkg github.com/99designs/gqlgen - package {{ .PackageName }} import ( diff --git a/codegen/testserver/resolver.go b/codegen/testserver/resolver.go index a5ef197d702..f7d04e16cb4 100644 --- a/codegen/testserver/resolver.go +++ b/codegen/testserver/resolver.go @@ -1,5 +1,3 @@ -//go:generate gorunpkg github.com/99designs/gqlgen - package testserver import ( diff --git a/docs/content/getting-started.md b/docs/content/getting-started.md index fe7e3ae393d..285a84478d8 100644 --- a/docs/content/getting-started.md +++ b/docs/content/getting-started.md @@ -1,37 +1,60 @@ --- linkTitle: Getting Started -title: Building graphql servers in golang -description: Get started building type-safe graphql servers in Golang using gqlgen +title: Building GraphQL servers in golang +description: Get started building type-safe GraphQL servers in Golang using gqlgen menu: main weight: -5 --- -## Goal +This tutorial will take you through the process of building a GraphQL server with gqlgen that can: -The aim for this tutorial is to build a "todo" graphql server that can: - - - get a list of all todos - - create new todos - - mark off todos as they are completed + - Return a list of todos + - Create new todos + - Mark off todos as they are completed You can find the finished code for this tutorial [here](https://github.com/vektah/gqlgen-tutorials/tree/master/gettingstarted) ## Install gqlgen -Assuming you already have a working [go environment](https://golang.org/doc/install) you can simply go get: +This article uses [`dep`](https://github.com/golang/dep) to install gqlgen. [Follow the instructions for your environment](https://github.com/golang/dep) to install. + +Assuming you already have a working [Go environment](https://golang.org/doc/install), create a directory for the project in your `$GOPATH`: + +```sh +$ mkdir -p $GOPATH/src/github.com/[username]/gqlgen-todos +``` + +> Go Modules +> +> Currently `gqlgen` does not support Go Modules. This is due to the [`loader`](https://godoc.org/golang.org/x/tools/go/loader) package, that also does not yet support Go Modules. We are looking at solutions to this and the issue is tracked in Github. + +Add the following file to your project under `scripts/gqlgen.go`: + +```go +// +build ignore + +package main + +import "github.com/99designs/gqlgen/cmd" + +func main() { + cmd.Execute() +} +``` + +Lastly, initialise dep. This will inspect any imports you have in your project, and pull down the latest tagged release. ```sh -go get -u github.com/99designs/gqlgen github.com/vektah/gorunpkg +$ dep init ``` ## Building the server ### Define the schema -gqlgen is a schema-first library, so before touching any code we write out the API we want using the graphql -[Schema Definition Language](http://graphql.org/learn/schema/). This usually goes into a file called schema.graphql +gqlgen is a schema-first library — before writing code, you describe your API using the GraphQL +[Schema Definition Language](http://graphql.org/learn/schema/). This usually goes into a file called `schema.graphql`: -`schema.graphql` ```graphql type Todo { id: ID! @@ -60,23 +83,29 @@ type Mutation { ``` ### Create the project skeleton + ```bash -$ gqlgen init -Exec "go run ./server/server.go" to start GraphQL server +$ go run scripts/gqlgen.go init ``` -This has created an empty skeleton with all files we need: +This has created an empty skeleton with all files you need: + + - `gqlgen.yml` — The gqlgen config file, knobs for controlling the generated code. + - `generated.go` — The GraphQL execution runtime, the bulk of the generated code. + - `models_gen.go` — Generated models required to build the graph. Often you will override these with your own models. Still very useful for input types. + - `resolver.go` — This is where your application code lives. `generated.go` will call into this to get the data the user has requested. + - `server/server.go` — This is a minimal entry point that sets up an `http.Handler` to the generated GraphQL server. + + Now run dep ensure, so that we can ensure that the newly generated code's dependencies are all present: - - gqlgen.yml - The gqlgen config file, knobs for controlling the generated code. - - generated.go - The graphql execution runtime, the bulk of the generated code - - models_gen.go - Generated models required to build the graph. Often you will override these with models you write yourself. Still very useful for input types. - - resolver.go - This is where your application code lives. generated.go will call into this to get the data the user has requested. + ```sh + $ dep ensure + ``` ### Create the database models -The generated model for Todo isnt quite right, it has a user embeded in it but we only want to fetch it if the user actually requested it. So lets make our own. +The generated model for Todo isn't right, it has a user embeded in it but we only want to fetch it if the user actually requested it. So instead lets make a new model in `todo.go`: -`todo.go` ```go package gettingstarted @@ -88,22 +117,27 @@ type Todo struct { } ``` -And then tell gqlgen to use this new struct by adding this to the gqlgen.yml: +Next tell gqlgen to use this new struct by adding it to `gqlgen.yml`: + ```yaml models: Todo: - model: github.com/vektah/gqlgen-tutorials/gettingstarted.Todo + model: github.com/[username]/gqlgen-todos/gettingstarted.Todo ``` -and regenerate by running +Regenerate by running: + ```bash -$ gqlgen -v -Unable to bind Todo.user to github.com/vektah/gqlgen-tutorials/gettingstarted.Todo +$ go run scripts/gqlgen.go -v +Unable to bind Todo.user to github.com/[username]/gqlgen-todos/gettingstarted.Todo no method named user no field named user Adding resolver method ``` -*note* we've used the verbose flag here to show what gqlgen is doing. Its looked at all the fields on our model and found matching methods for all of them, except user. For user it added a resolver to the interface we need to implement. This is the magic that makes gqlgen work so well. + +> Note +> +> The verbose flag `-v` is here to show what gqlgen is doing. It has looked at all the fields on the model and found matching methods for all of them, except user. For user it has added a resolver to the interface you need to implement. *This is the magic that makes gqlgen work so well!* ### Implement the resolvers @@ -151,18 +185,17 @@ better than generating. ### Write the resolvers -This is a work in progress, we have a way to generate resolver stubs, but it cannot currently update existing code. We can force it to run again by deleting `resolvers.go` and re-running gqlgen: +This is a work in progress, we have a way to generate resolver stubs, but it cannot currently update existing code. We can force it to run again by deleting `resolver.go` and re-running gqlgen: + ```bash -rm resolvers.go -gqlgen +$ rm resolver.go +$ go run scripts/gqlgen.go ``` -Now we just need to fill in the `not implemented` parts - +Now we just need to fill in the `not implemented` parts. Update `graph/graph.go` -`graph/graph.go` ```go -//go:generate gorunpkg github.com/99designs/gqlgen +//go:generate go run ./scripts/gqlgen.go package gettingstarted @@ -242,34 +275,14 @@ query findTodos { ## Finishing touches -gqlgen is still unstable, and the APIs may change at any time. To prevent changes from ruining your day make sure -to lock your dependencies: - -*Note*: If you dont have dep installed yet, you can get it [here](https://github.com/golang/dep) - -First uninstall the global version we grabbed earlier. This is a good way to prevent version mismatch footguns. - -```bash -rm ~/go/bin/gqlgen -rm -rf ~/go/src/github.com/99designs/gqlgen -``` +At the top of our `resolver.go` add the following line: -Next install gorunpkg, its kind of like npx but only searches vendor. - -```bash -dep init -dep ensure -``` - -At the top of our resolvers.go a go generate command was added that looks like this: ```go -//go:generate gorunpkg github.com/99designs/gqlgen +//go:generate go run scripts/gqlgen.go -v ``` -This magic comment tells `go generate` what command to run when we want to regenerate our code. to do so run: +This magic comment tells `go generate` what command to run when we want to regenerate our code. To run go generate recursively over your entire project, use this command: + ```go go generate ./... -``` - -*gorunpkg* will build and run the version of gqlgen we just installed into vendor with dep. This makes sure that everyone working on your project generates code the same way regardless which binaries are installed in their gopath. - +``` diff --git a/docs/static/main.css b/docs/static/main.css index e4b06d2062a..782bf8d8d54 100644 --- a/docs/static/main.css +++ b/docs/static/main.css @@ -20,6 +20,11 @@ --color-code-text: #445; --color-code-background: #f5f9fc; + + --color-blockquote-background: #fffaf3; + --color-blockquote-highlight: rgba(0, 0, 0, 0.1); + + --margin-default: 15px; } html, body, div, span, applet, object, iframe, @@ -48,7 +53,7 @@ footer, header, hgroup, menu, nav, section { display: block; } ol, ul { - margin-bottom: 1em; + margin-bottom: var(--margin-default); list-style: disc; margin-left: 1.5em; } @@ -80,7 +85,7 @@ a { } a:hover { - border-bottom: 2px solid; + text-decoration: underline; } @media (min-width: 768px) { @@ -97,7 +102,7 @@ a:hover { main { flex: 1; - padding: 20px; + padding: 0 20px 20px; color: var(--color-text); } @@ -107,6 +112,10 @@ main { margin: auto; } +main .content { + margin-top: 40px; +} + header { grid-area: header; background: var(--color-heading-background); @@ -145,20 +154,20 @@ h1 { h2 { margin-top: 2em; - margin-bottom: 1em; + margin-bottom: var(--margin-default); font-size: 19px; font-weight: 700; } h3 { margin-top: 1.5em; - margin-bottom: 1em; + margin-bottom: var(--margin-default); font-size: 16px; font-weight: 500; } p { - margin-bottom: 1em; + margin-bottom: var(--margin-default); } nav { @@ -178,7 +187,7 @@ nav { } .menu a:hover { - border-bottom: none; + text-decoration: none; } .menu-item { @@ -243,7 +252,7 @@ ul.menu a:hover { } .logo:hover { - border-bottom: none; + text-decoration: none; } code { @@ -252,18 +261,25 @@ code { font-weight: 500; color: var(--color-code-text); background-color: var(--color-code-background); + border-radius: 3px; + font-size: 13px; + margin-bottom: var(--margin-default); } strong { font-weight: 700; } +em { + font-style: italic; +} + .anchor-link { display: inline-block; } .anchor-link:hover { - border-bottom: none; + text-decoration: none; } .anchor-icon { @@ -392,3 +408,28 @@ strong { } } + + +blockquote { + background-color: var(--color-blockquote-background); + border-left-color: var(--color-blockquote-highlight); + border-left-width: 9px; + border-left-style: solid; + padding: 1em 20px 1em 11px; + margin-bottom: var(--margin-default); + margin-left: -20px; + margin-right: -20px; +} + +blockquote p { + margin-bottom: 0; +} + +/* Blockquote headings. */ +blockquote p:first-of-type { + font-weight: bold; +} + +blockquote code { + background-color: var(--color-blockquote-highlight); +} \ No newline at end of file diff --git a/docs/static/syntax.css b/docs/static/syntax.css index 2e0b2e9defe..8e239fd5dd2 100644 --- a/docs/static/syntax.css +++ b/docs/static/syntax.css @@ -60,7 +60,6 @@ overflow: auto; display: block; padding: 5px 10px; - margin-bottom: 1em; font-family: var(--font-code); color: var(--color-code-text); background-color: var(--color-code-background);