LoginSignup
10
4

More than 1 year has passed since last update.

GitHub Issue formsでリッチな入力フォームを作成する

Last updated at Posted at 2021-10-31

GitHubに、Issue formsというリッチなissue入力フォームを作成する機能が今年(2021年)6月に加わったので、その紹介をします。この機能を用いると、小項目に分かれた(構造化された)フォームを提供でき、そこではテキストエリア以外にチェックボックスの利用や必須項目の指定なども可能です。つまり、Google Formsで作れるような入力フォームを提供できます。

なお、この記事の執筆時点(2021年10月)では、この機能は公開リポジトリのみで利用可能なベータ版という位置づけです。広く使われてフィードバックが十分に集まり、Organization内のプライベートリポジトリでも利用可能な状態になってくれるとありがたいと思ったので、この記事を書いています。

用語

  • 入力者:フォームを用いてissueを報告する人(フォームのユーザ)を指します。
  • フォーム画面:issueのフォームが表示される入力画面のことを、ここではこのように呼ぶことにします。

GitHub Issuesのデフォルトの入力フォーム

みなさんご存知のとおりですが、GitHub Issuesは、デフォルトでは単一の広いテキストエリアでしかないフォームを提供しています。

Markdownで自由に入力できるので、様々なフィードバックを気兼ねなく入力してもらうにはとても便利です。他方で、集めたい情報が明確な場合は必ずしも使いやすいわけではありません。たとえば、こういったケースがあります。

  • 入力してほしいと思った情報が必ずしも含まれていない。当然ながら、足りない情報を伝えて再度入力してもらう、といったやりとりが必要になってしまう。
  • 入力してほしいと思った情報が含まれていたとしても、ある入力者は冒頭に書き、ある入力者は最後に書き、ある入力者は長い文章の中に含め、とバラバラ。中の人が情報をパッと把握するという観点からは扱いにくい。

Issueテンプレート

この利便性の問題を緩和するために、長らく、GitHub Issueテンプレートという機能が使われていました。これは、テンプレートをMarkdownで用意することで、そのテンプレートに従って入力してもらうというものです。入力内容に含めてほしい情報や明確にしてほしいポイントを入力者に伝えることが可能なので、協力的な入力者であれば、初見であっても一発で適切な情報を集めることが可能です。

GitHub Issueテンプレートを使うには、 .github/ISSUE_TEMPLATE/ というディレクトリをリポジトリルート直下に作成し、その中にMarkdownファイルを配置します。バグ報告や新機能提案など、issue報告が必要になるケースはたいてい複数存在し、含めてほしい情報はそれぞれのケースで異なりますので、複数のMarkdownファイルを配置できます。

テンプレートの設定例

階層構造:

.
├── .github
│   └── ISSUE_TEMPLATE
│       ├── bug_report.md
│       ├── enhancement_request.md
:

設定内容(の最初の10行ずつ):

%  head .github/ISSUE_TEMPLATE/bug_report.md
---
name: Bug report
about: Create a report to help us improve
labels: bug
---

<!-- Thanks for filing a 🐛 bug report 😄! -->

**Problem**
<!-- A clear and concise description of what the bug is. -->
%  head .github/ISSUE_TEMPLATE/enhancement_request.md
---
name: Enhancement request
about: Suggest an enhancement for this project
labels: enhancement
---

 <!-- Thanks for filing an 🙋 enhancement request 😄! -->

**Describe the problem you are trying to solve**
<!-- A clear and concise description of the problem this enhancement request is trying to solve. -->

上記テンプレートの実際の見え方

入力者が「New issue」のボタンを押すと、報告したいissueのカテゴリを選ぶ画面が表示されます。ここには、各テンプレートのヘッダのnameおよびaboutの情報が使われます。

ss 2021-10-31 17.45.56.png

選ぶと、フォーム画面はこのようになっています。

github.com_rust-lang_rustup_issues_new_assignees=&labels=bug&template=bug_report.md(1024x768).png

入力内容としてテンプレートの内容が予め設定されていることがわかります。また、テンプレートヘッダのlabelsで設定したラベル(bug)が自動的に設定されています。

Issueテンプレートの少し不便な点

Issueテンプレートは長らく使われており、全般的にはとてもよく機能していたと思います。
しかし、これはあくまでテンプレートなので、少し不便な点もあったように思います。

  • Markdownによるテンプレートの表現方法が、テンプレート作成者によって異なる
    • 説明をHTMLのコメント(<!-- ... -->)で表現する人もいれば、平文にする人もいる
  • 報告時に、自分の入力で上書きしてしまったほうがよいのか、テンプレートのテキストを残した上で追記したほうがよいのか、入力者が迷うケースがある
    • テンプレートのテキストが例示と思われる部分は、たいていの人は上書きする
    • テンプレートのテキストが説明と思われる部分は、残す人も削除する人もいるし、残すか迷うケースもある
  • いざとなれば入力者がテンプレートを完全に無視して自由に入力できる(入力欄に最初に入っているテキストを消去して一から書けばよい)
  • (細かいですが)項目の表現方法がテンプレート作成者によってまちまちで、人間の認識力に依存している
    • ### で表現するケース
    • #### など、より下位の見出しを用いるケース(### だとフォントが大きくなりすぎるので?)
    • 太字として表示するケース

Issue forms

Issue formsは、(おそらく)こういった不便な点を解消し、よりよいユーザ体験を提供するために作られました。具体的には、issueを入力項目に細分化し、各項目に見出しや説明を加えることが可能です。各入力項目には、テキスト入力以外に選択式(チェックボックスやドロップダウン)も使えます。また、入力を伴わない、単なる説明の表示のためのブロックを加えることもでき、そこにはMarkdownでリンクや箇条書きなども含めることが可能です。

実例

具体的に見たほうが早いと思うので、例を載せます。

例1:github/docsリポジトリのimprove-existing-docs

github/docsリポジトリのimprove-existing-docsではこのようなフォームが実現されています。自分のブラウザで見たい方はこちらからアクセスできます。

スクリーンショット:

github.com_github_docs_issues_new_assignees=&labels=content&template=improve-existing-docs.yaml(1024x768).png

これを実現するためのYAML:

name: Improve existing content
description: Make a suggestion to improve the content in an existing article.
labels:
  - content
body:
  - type: markdown
    attributes:
      value: |
        **HUBBERS!!** This is the github/docs open source repo. You may want to open an issue in the internal-only github/docs-content repo instead.

        * For questions, ask in [Discussions](https://github.com/github/docs/discussions).
        * Before you file an issue read the [Contributing guide](https://github.com/github/docs/blob/main/CONTRIBUTING.md).
        * Check to make sure someone hasn't already opened a similar [issue](https://github.com/github/docs/issues).

  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: This project has a Code of Conduct that all participants are expected to understand and follow.
      options:
        - label: I have read and agree to the GitHub Docs project's [Code of Conduct](https://github.com/github/docs/blob/main/CODE_OF_CONDUCT.md)
          required: true

  - type: textarea
    attributes:
      label: What article on docs.github.com is affected?
      description: Please link to the article you'd like to see updated.
    validations:
      required: true

  - type: textarea
    attributes:
      label: What part(s) of the article would you like to see updated?
      description: |
        - Give as much detail as you can to help us understand the change you want to see. 
        - Why should the docs be changed? What use cases does it support? 
        - What is the expected outcome?
    validations:
      required: true

  - type: textarea
    attributes:
      label: Additional information
      description: Add any other context or screenshots about the feature request here.
    validations:
      required: false

また、このフォームを用いて送信、登録された内容(登録内容)は、たとえばこんな感じになります。

登録内容の例:

github.com_github_docs_issues_11509(1024x768).png

例2:pandas-dev/pandasリポジトリのinstallation_issue

pandas-dev/pandasリポジトリのinstallation_issueではこのようなフォームが実現されています。自分のブラウザで見たい方はこちらからアクセスできます。

スクリーンショット:

github.com_pandas-dev_pandas_issues_new_assignees=&labels=Build%2CNeeds+Triage&template=installation_issue.yaml&title=BUILD%3A+(1024x768).png

これを実現するためのYAML:

name: Installation Issue
description: Report issues installing the pandas library on your system
title: "BUILD: "
labels: [Build, Needs Triage]

body:
  - type: checkboxes
    id: checks
    attributes:
      options:
        - label: >
            I have read the [installation guide](https://pandas.pydata.org/pandas-docs/stable/getting_started/install.html#installing-pandas).
          required: true
  - type: input
    id: platform
    attributes:
      label: Platform
      description: >
        Please provide the output of ``import platform; print(platform.platform())``
    validations:
      required: true
  - type: dropdown
    id: method
    attributes:
      label: Installation Method
      description: >
        Please provide how you tried to install pandas from a clean environment.
      options:
        - pip install
        - conda install
        - apt-get install
        - Built from source
        - Other
    validations:
      required: true
  - type: input
    id: pandas
    attributes:
      label: pandas Version
      description: >
        Please provide the version of pandas you are trying to install.
    validations:
      required: true
  - type: input
    id: python
    attributes:
      label: Python Version
      description: >
        Please provide the installed version of Python.
    validations:
      required: true
  - type: textarea
    id: logs
    attributes:
      label: Installation Logs
      description: >
        If possible, please copy and paste the installation logs when attempting to install pandas.
      value: >
        <details>


        Replace this line with the installation logs.


        </details>

Issue formsの設定(YAML)の文法

設定と実際の表示を見比べれば、どの設定がどの表示に対応しているかは簡単にわかると思いますが、一応、簡単に説明します。基本的には例1を題材として扱いますが、所々例2も使います。

トップレベルの要素

例1で、(body以外の)トップレベルの要素は次のようになっていました。

name: Improve existing content
description: Make a suggestion to improve the content in an existing article.
labels:
  - content

これらのトップレベル要素は、フォーム全体の属性を扱います。

name はこのフォーム全体の名前です。元々のMarkdownテンプレートの name と同様、入力者が「New issue」のボタンを押した際に、カテゴリ選択の画面でカテゴリ名として使われます。

description はこのフォーム全体の説明です。元々のMarkdownテンプレートの about に相当します。入力者が「New issue」のボタンを押した際に、カテゴリ選択の画面でカテゴリの説明として使われます。

labels はこのフォームを用いて送信したissueに自動的に設定するラベルの一覧です。元々のMarkdownテンプレートの labels と同じですが、YAMLのリスト表現になっています。

これら以外に、 assignees というキーを用いて、issueを誰にアサインするかを設定できます。

また、例2では、次のように title というキーも含まれていました。

title: "BUILD: "

これは、フォーム画面を開いた際にissueのタイトル入力欄に自動的に設定される初期値です。issueテンプレートでもヘッダのtitleフィールドで指定可能でした。プレースホルダー(例示のために薄色で表示され、ユーザが実際に入力すると消えるテキスト)ではなく、初期値となっているので、たとえばこの場合、入力者は「BUILD: 」というテキストに続けて実際のタイトルを入力することが期待されています。

フォーム要素

トップレベルのbodyに設定された内容がフォームの本体(ボディ)となり、実際のフォーム画面の表示に使われます。ボディの値は、個々のパーツ(フォーム要素)のリストとして設定する必要があります。フォーム要素とは、前述の「入力項目」とほぼ同じです(この後説明するように、入力を伴わない項目も表現できます)。

個々のフォーム要素には、 typeattributes の2つのキーを必ず設定する必要があります。type は要素の種類(タイプ)を指定するもの、 attributes は属性です。

ほかに idvalidations も任意で設定できます。idはフォーム内での識別子なので、フォーム内でユニークでなくてはいけません(同じ値を複数使えないということです)。idの役割については後述します。

フォーム要素にはいくつかのタイプがあるので、先程の例を用いて、フォーム要素のタイプを1つずつ見ていきましょう。

markdownタイプ

  - type: markdown
    attributes:
      value: |
        **HUBBERS!!** This is the github/docs open source repo. You may want to open an issue in the internal-only github/docs-content repo instead.

        * For questions, ask in [Discussions](https://github.com/github/docs/discussions).
        * Before you file an issue read the [Contributing guide](https://github.com/github/docs/blob/main/CONTRIBUTING.md).
        * Check to make sure someone hasn't already opened a similar [issue](https://github.com/github/docs/issues).

markdown タイプのフォーム要素は、ユーザの入力を伴わないブロックです。この例にあるように、リンクや箇条書きもMarkdownで記載できるので、入力前に入力者に読んでおいてほしい内容、入力者へのメッセージなどがあれば、これを使うのがよいでしょう。

attributesvalueの値がそのまま表示に使われます。YAMLの記法で、複数行のMarkdownをYAML内に埋め込んでいます。

checkboxesタイプ

  - type: checkboxes
    id: terms
    attributes:
      label: Code of Conduct
      description: This project has a Code of Conduct that all participants are expected to understand and follow.
      options:
        - label: I have read and agree to the GitHub Docs project's [Code of Conduct](https://github.com/github/docs/blob/main/CODE_OF_CONDUCT.md)
          required: true

checkboxes タイプのフォーム要素は、その名前のとおりチェックボックスを入力欄とする入力項目です。チェックボックスなので、フォーム作成者は選択肢を複数入れることができ、入力者も複数のボックスにチェックを入れることが可能です。

選択肢はattributesoptionsにリストで設定します。optionsの各選択項目にはlabelでその選択肢を表すテキストを設定する必要があります。

各選択項目は、requiredという任意のキーを用いて、チェックを入れることを必須にできます。requiredが設定された場合、チェックを入れなければフォームを送信できません。これは、入力者に、さまざまな入力前の同意や確認をしてもらうのによく使われます。なお、Google Formsなどと同様に、必須項目の右肩には * マークもつきます。

attributeslabeldescriptionは、それぞれフォーム要素の見出しと説明に使われます。これらはmarkdownタイプ以外のフォーム要素に共通です(必須か任意かは、フォーム要素のタイプによって少し違います)。なお、上の登録内容の例を見て気づいたかもしれませんが、descriptionは送信、登録された内容には含まれません。

textareaタイプ

  - type: textarea
    attributes:
      label: What article on docs.github.com is affected?
      description: Please link to the article you'd like to see updated.
    validations:
      required: true

textarea タイプのフォーム要素は、その名前のとおりテキストエリアを入力欄とする入力項目です。attributeslabeldescriptionは、checkboxesのところで説明したとおり、それぞれフォーム要素の見出しと説明として表示されます。ちなみに、見出しは、入力者がissueを送信して登録される際にはMarkdownの見出しレベル3(###)になるようです。

上の例のフォーム要素にはvalidationsというキーが含まれており、required: trueとなっています。これは、入力を必須とするためのものです。つまり、この「What article on docs.github.com is affected?」というフォーム要素のテキストエリアが空のままでは、フォームを送信できません。必須項目の右肩に * マークがつくのはチェックボックスの場合と同じです。

例1と例2で使われていないキーを2つ紹介しましょう。次の設定は、pandas-dev/pandasのbug_report.yamlからとってきた例です。

  - type: textarea
    id: example
    attributes:
      label: Reproducible Example
      description: >
        Please follow [this guide](https://matthewrocklin.com/blog/work/2018/02/28/minimal-bug-reports) on how to
        provide a minimal, copy-pastable example.
      placeholder: >
        import pandas as pd

        df = pd.DataFrame(range(5))

        ...
      render: python

ここではplaceholderというキーがattributesに含まれています。これは、フォーム画面を開いた際にテキストエリアに設定されるプレースホルダーです。プレースホルダーなので、ユーザが実際に入力を始めれば消えます。基本的には、説明だと伝わりにくい入力内容の例示に使うことが多いでしょう。

また、renderというキーもattributesに含まれています。これは、この入力内容をどのように表示してほしいかを設定するものです。ここでは python と指定しているので、入力内容にはPythonの文法に従って色がつけられます。そのため、たとえばフォームを送信するとこのような表示になります。

ss 2021-10-31 1.12.59.png

なお、どのような言語が指定できるかは、Linguistの設定を見るとわかります。この言語指定は、GitHub全体で、Markdown内の各コードブロックを表示する際に、特定の言語の文法に従って色をつけるのにも使われるものなので、慣れている人も多いでしょう。

inputタイプ

inputタイプのフォーム要素は例1では使われていないので、例2のフォーム要素を題材として使います。

  - type: input
    id: platform
    attributes:
      label: Platform
      description: >
        Please provide the output of ``import platform; print(platform.platform())``
    validations:
      required: true

inputタイプのフォーム要素は、1行のテキスト入力欄を扱うフォーム要素です。長い文章の不要な、1行だけで済む内容にはtextareaではなくinputを使うのがよいでしょう。扱いはtextareaとほぼ同じですが、唯一、renderが指定できません。

dropdownタイプ

dropdownタイプのフォーム要素も例1では使われていないので、例2のフォーム要素を題材として使います。

  - type: dropdown
    id: method
    attributes:
      label: Installation Method
      description: >
        Please provide how you tried to install pandas from a clean environment.
      options:
        - pip install
        - conda install
        - apt-get install
        - Built from source
        - Other
    validations:
      required: true

dropdownタイプのフォーム要素は、ドロップダウンメニューを入力欄とするフォーム要素です。

選択式という点で、ドロップダウンメニューはチェックボックスに似ていますが、設定方法はやや異なります。checkboxesの場合、optionsに指定する各選択項目では、テキストはlabelという子要素の値として設定しており、このほかに必須項目を指定するrequiredというキーが使えました。dropdownの場合は、optionsに指定する各選択項目はテキストそのものです。

id

ここまで読んできて、フォーム要素のidの役割がわからないというかたもいるでしょう。idは何の表示にも関わっていません。識別子だと説明しましたが、フォーム要素の識別が必要な場面は出てきませんでしたし、任意なので、指定しなくても動作上は問題ありません。

実はこれは、フォーム画面を開いた際にフォーム要素に特定の値を自動的に設定するのに使われます。たとえば、例2で用いたpandas-dev/pandasのinstallation_issueにおいて、Python Versionの入力欄を提供するフォーム要素にはidとしてpythonが指定されているので、これを使ってみましょう。

pandas-dev/pandasのinstallation_issueの場合、Web UIで「New issue」を選び、報告のカテゴリとして「Installation Issue」を選ぶとフォーム画面が開きますが、そのフォーム画面のURLは次のようになっているはずです。

https://github.com/pandas-dev/pandas/issues/new?assignees=&labels=Build%2CNeeds+Triage&template=installation_issue.yaml&title=BUILD%3A+

ここで、pythonパラメータの値を3.10に設定する&python=3.10という文字列をURLのクエリ文字列に加えると、URLは次のようになります。

https://github.com/pandas-dev/pandas/issues/new?assignees=&labels=Build%2CNeeds+Triage&template=installation_issue.yaml&title=BUILD%3A+&python=3.10

このURLから開いたフォーム画面では、Python Versionの入力欄に最初から3.10というテキストが入ります。

ss 2021-10-31 4.13.39.png

このように、idを設定しておくと、URLを通じてフォームに値を最初から設定できるようになります。GitHub IssuesのWeb UIを通じて操作するだけならこのようなidの設定をしても特にメリットはありませんが、プログラムでURLを生成してフォーム画面を開くなど、使い方によっては操作の簡略化が可能になります。使う可能性が少しでもあるなら設定しておくほうがよいでしょう。

なお、説明に用いたpandas-dev/pandasのinstallation_issueのURLですが、わかりやすくするためにクエリ文字列をYAMLで表現すると、次のようになります。

assignees: ""
labels: "Build,Needs Triage"
template: installation_issue.yaml
title: "BUILD: "

installation_issue.yamlに設定されたlabelstitle、さらには設定が省略された(空の)assigneesが埋め込まれており、labelsassigneesはフォームの属性を設定するのに使われ、titleは、フォーム画面を開いた際にタイトルにテキストを自動設定するのに使われていることがわかります。
したがって、クエリ文字列を変更すればこれらの動作も変更できます。

Issue formsの実務的な話題

Issue formsの文法や機能はすべて説明したので、実務的な話題をいくつか取り上げます。

設定の編集環境

Issue formsのYAMLの設定ファイルは、文法も簡単ですし、設定ファイルのサイズも小さいので、編集には自分の好きな環境を使えばよいでしょう。GitHubのWeb UIで作業する人が多いと思いますが、私はローカルのVisual Studio Codeで編集しました。

Visual Studio Codeの場合、.github/ISSUE_TEMPLATEディレクトリ内のYAMLファイルはIssue formsの文法に従う必要があることを理解してくれており、自動的に補完が効きます。

たとえば name などのキーは補完してくれます。また、 body に新たなフォーム要素を加えると自動的に type: まで入れてくれる、 aattributes を補完入力すると自動的に value: まで入れてくれる、など、それなりに便利です(うまく認識せずやり直したこともたまにありましたが、まぁ大きな問題ではありません)。

ss 2021-10-31 14.30.47.png

ss 2021-10-31 14.29.41.png

ss 2021-10-31 14.32.28.png

ss 2021-10-31 14.33.52.png

今のところプレビューや文法のvalidationはローカルではできないので、YAMLファイルを編集したらGitで適当なブランチにコミットし、GitHub上のリポジトリにpushします。GitHubのWeb UIでそのブランチに移動してYAMLファイルを開くと、YAMLで書かれたソースではなく入力画面のプレビューを表示してくれるはずです(トップレベルのbody以外は上部にテーブルで表示されます)。文法上の問題があれば表示してくれるようです。

GitHubのWeb UIでIssue formsのYAMLファイルを表示した様子:

ss 2021-10-31 15.00.18.png

Web UIで作業していてもプレビューは問題になっているようなので、今後改善が入るかもしれません。

IssueテンプレートのMarkdownから移行すべきか?

入力してもらいたい項目(入力内容の構造)が完全に定形になっているなら、IssueテンプレートのMarkdownから移行するほうがよいでしょう。他方で、少し自由度を残したければ、無理に移行しなくてもいいとは思います。

また、現時点でIssue formsでは条件分岐ができません。Issueテンプレートによっては、「先程の質問でこのように回答した人は、このセクションは削除してください」のように条件分岐していることがありますが、現時点でそれはIssue formsで表現できないということです。なので、そういった使い方をしている場合は無理に移行する必要はないかと思います。

とはいえ、Issue formsはまだベータ版なので、条件分岐のサポートが今後入る可能性もあります。実際に、フィードバックとして要望は出ていますので、今後に期待しましょう。

なお、.github/ISSUE_TEMPLATE以下に複数のIssueテンプレートが存在する場合、すべてをIssueテンプレートとIssue formsのどちらかに統一する必要はありません。一部はIssueテンプレートで実装し、一部はIssue formsで実装しても問題ありません。ただし、ファイル名の拡張子を除いた部分で識別されるので、たとえば、.github/ISSUE_TEMPLATE/foobar.mdというIssueテンプレートがある場合、.github/ISSUE_TEMPLATE/foobar.yamlというIssue formsは置けません。

IssueテンプレートのMarkdownからの移行

上で説明したとおりなので、Markdownからの移行は簡単です。たいていは、こんな感じでほぼ機械的に移行できるはずです。

  • ヘッダのnametitle → そのままトップレベルのnametitle
  • ヘッダのabout → トップレベルのdescription
  • ヘッダのlabelsassignees → YAMLのリストに変更してトップレベルのlabelsassignees
  • ボディ → フォーム要素に分解してbodyに配列として入れる(たいていは配列内のフォーム要素はtextareaタイプが多くなるはず)
  • 各フォームの見出し → 各フォーム要素のlabel
  • 各フォームの説明 → 各フォーム要素のdescription
  • 各フォームの例示 → 各テキストエリアのplaceholder

今後の期待

Issue formsに関して、私の今後の期待を書いておきます。

抜け道の防止

現在、Issue formsを作成しても、様々な抜け道があります。これらの抜け道を防がないと、場合によっては効果が薄いかもしれません。

たとえば、Web UIに限っても、Issuesの画面の「New issue」を使う以外に、さまざまな方法があります。また、現時点でiOSのGitHubアプリではIssueテンプレートもIssue formsも未対応で、すべてベタ書きのIssueにフォールバックされてしまいます。加えて、一旦登録された後はMarkdownとなるので、再編集の機能を使えば、いくらでも変更が効きます。

これらは開発側も認識しているようなので、そのうち対策が入るかもしれません。

条件分岐のサポート

移行すべきかの議論で書いたように、今のところ、Issue formsでは条件分岐はできません。しかし、Issueテンプレートに複数のケースのテンプレートを含めておき、自分に該当するものだけ選んで入力し、後は消去してもらう、というやり方をしているフォームは実際に多少存在します。こういったものが扱えるようになるとよりよいですね。

バージョン

  • GitHub Issue formsおよび仕様:2021年10月31日現在のもの
  • Visual Studio Code: 1.61.2

参考資料

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4