20200426のCSSに関する記事は6件です。

学習記録 #2 モーダル実装

行ったこと

- HTML / CSS / JavaScript
- サービス作成

つまずいたこと

- モーダルの実装

HTML

      <!-- モーダル -->
      <div id="modal">
        <div class="modal-content">
          <div class="modal-body">
            <h1>hello</h1>
            <button id="closeButton">Close</button>
          </div>
        </div>
      </div>
      <!-- モーダル表示用ボタン -->
      <button id="openButton">Open Modal</button>

CSS

#modal{
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
height: 100%;
width: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.5);
}

.modal-content{
background-color: white;
width: 500px;
margin: 15% auto;
}

JavaScript

const modal = document.getElementById('modal');
const openButton = document.getElementById('openButton');
const closeButton = document.getElementById('closeButton');


openButton.addEventListener('click', () => {
  modal.style.display = 'block';
});

closeButton.addEventListener('click', () => {
  modal.style.display = 'none';
});

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学習記録 #2 モーダル実装(Bootstrap不使用)

行ったこと

- HTML / CSS / JavaScript
- サービス作成

つまずいたこと

- モーダルの実装

HTML

      <!-- モーダル -->
      <div id="modal">
        <div class="modal-content">
          <div class="modal-body">
            <h1>hello</h1>
            <button id="closeButton">Close</button>
          </div>
        </div>
      </div>
      <!-- モーダル表示用ボタン -->
      <button id="openButton">Open Modal</button>

CSS

#modal{
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
height: 100%;
width: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.5);
}

.modal-content{
background-color: white;
width: 500px;
margin: 15% auto;
}

JavaScript

const modal = document.getElementById('modal');
const openButton = document.getElementById('openButton');
const closeButton = document.getElementById('closeButton');


openButton.addEventListener('click', () => {
  modal.style.display = 'block';
});

closeButton.addEventListener('click', () => {
  modal.style.display = 'none';
});

修正点等ございましたら教えていただけますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】deviseを用いたユーザー新規登録機能の実装(基礎〜少し応用)

はじめに

Ruby on Rails を用いてWEBアプリを作成中です。
ユーザー新規登録の機能を実装するために、「devise」というgemを用いることにしました。
備忘もかねて、本文に実装した内容を残しておきます。
errorで困ったポイントも残しておきますので、参考にしていただけると幸いです^^

専門用語や参考にさせていただいた記事は最後に記載させていただいております。
「これ、なんで記述してるんだろう?」と、私自身が初めて作成した時に感じたポイントでもありますので、
初心者の方は気になるところかとも思います。
そちらも参照していただければと思います。

今回の方法はあくまで一つの実装方法でしかなく、やり方は色々あるのと、
私もまだ未熟なため、わかりにくい記述や余分な記述もございますし、調べきれてない、試しきれてないところもございます。
共にブラッシュアップしていければとも思ってますので、ご指摘もいただければと思います。

記載しきれなかった部分は、随時更新か、別途記事を用意いたします。

実装の目標

今回は以下の画像のような表示を目標として実装しました。
全てを解説するのは大変だったので、「ニックネーム」「メールアドレス」「パスワード」「生年月日」のみに絞っておりますのでご了承ください。
カラムのカスタマイズ、エラー表示、パスワードの表示/非表示、生年月日の選択など、「devise」の基本だけでなく、少し工夫が必要な内容も網羅してるかと思いますので、参考になれば幸いです。
Screenshot from Gyazo
Screenshot from Gyazo

1. deviseのインストール手順

 1-1. Gemfileに追記

(前略)
gem 'devise'
(後略)

※注意
「development、test、production」のgroup内に記述すると特定の環境でのみ使用する設定となりますので、group外に記述するようにしてください。

 1-2. コマンドの実行(インストール)

$ bundle install
$ rails g devise:install

2. Userモデルを作成

インストール後、モデルとテーブルを作成するために、以下をターミナルで実行してください。
通常のモデル作成とはコマンドが違うので注意してください。

$ rails g devise user 

マイグレーションファイルとモデルファイルが出来るので、以下のように記述ください。

2020*****_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :nickname, null: false
      t.string :email, null: false, default: ""
      t.string :password, null: false, default: ""
      t.string :encrypted_password, null: false, default: ""
      t.date :birthday, null: false

      # 〜省略〜

    end

    add_index :users, :email, unique: true

    # 〜省略〜

  end
end
app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable,  :validatable

  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  VALID_PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?\d)[a-z\d]+\z/i

  # 〜省略〜

  validates :nickname, presence: true, length: { maximum: 20 }
  validates :email, presence: true, uniqueness: true, format: { with: VALID_EMAIL_REGEX }
  validates :password, presence: true, length: { minimum: 7 }, format: { with: VALID_PASSWORD_REGEX }
  validates :birthday, presence: true

  # 〜省略〜

end

モデル「user.rb」について
「VALID_EMAIL_REGEX」「VALID_PASSWORD_REGEX」は正規表現によって、特定の文字を弾くようにしております。
「presence: true」は記述することで、空で登録することを弾くようにしてます。
「length: { maximum: 20 }」と「length: { minimum: 7 }」は文字数制限です。

ここで、以下の「devise.rb」に記述されてる「config.password_length = 6..128」についても、次のように編集しておきます。
この数字は文字制限を表してます(つまり、minimumが「6」です)。
理由はモデル「user.rb」より、今回passwordは7文字以上としているため、minimumが「7」である必要があります。
また、こちらを編集後はサーバーの再起動をしないと、反映されませんのでご注意を。

「devise.rb」の「config.password_length = 6..128」を編集した理由は、そのままにするとモデル「user.rb」側のバリデーション「length: { minimum: 7 }」と「devise.rb」側のバリデーション「config.password_length = 6..128」が共存することになるため、2つのバリデーションに引っかかって、2つのエラー表示されてしまうからです。
どちらか片方を削除する方法もありかと思いますが、経験もかねて、どちらも残す方法しか試せておりません。

config/initializers/devise.rb
# frozen_string_literal: true

# Use this hook to configure devise mailer, warden hooks and so forth.
# Many of these configuration options can be set straight in your model.
Devise.setup do |config|
  Rails.application.credentials[:secret_key_base]

  # 〜省略〜

  config.password_length = 7..128

  # 〜省略〜

end

最後にmigrationファイルをデータベースに反映するために、以下のコマンドを実行。

$ rails db:migrate

3. コントローラーの編集

Viewなどを先に提示してもよかったのですが、エラー表示などについての説明を一緒にした方が良いと判断し、ルーティングやコントローラーの設定を先に説明いたします。

まずはコントローラーから。
今回はユーザー新規登録(サインアップ/sign_up)の実装についてですので、コントローラーは「application_controller.rb」のみで大丈夫です。
以下のように記載します。

「configure_permitted_parameters」メソッドの定義をしてますが、deviseをインストールすることでdevise_parameter_sanitizerのpermitメソッドが使えるようになります。これがストロングパラメータに該当する機能です。サインアップ時に入力された「nickname、email、password、birthday」のキーの内容の保存を許可しています。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    added_attrs = [ :nickname, :email, :password, :birthday ]
    devise_parameter_sanitizer.permit :sign_up, keys: added_attrs
  end
end

4. ルーティングの編集

以下のように記載してください。

注意点としては、「routes.rb」の順番です。
「users/:id」が「users/sign_in」を包括してエラーが発生しないように、「devise_for :users」を「resources :users」より先に書く必要があります。

また、deviseでサインアップする際に、例えばパスワードを忘れて保存しようとしてエラーが発生すると、親ページにリダイレクトされます。
つまり、サインアップページ「/users/sign_up」でエラーが発生した場合、「/users」にリダイレクトされてしまいます。
そのまま登録するとルーティングエラーが表示されます。

これを回避するために、「devise_scope :users」以下の記述を追記して、任意のルーティングをさせています。
方法はいくつかありますが、今回は手っ取り早そうです。

config/routes.rb
Rails.application.routes.draw do

  devise_for :users

  devise_scope :users do
    get '/users', to: redirect("/users/sign_up")
  end

  # 〜省略〜

end

5. Veiwの編集

まずは、以下のコマンドを実行してください。Modelに対応するViewが生成されます。
Modelには「users」などを入れる文献が多ので、以下は「$ rails g devise:views users」で実行してます。
私が学んだ某スクールで最初に学んだ時は「devise」でしたので、ユーザーマイページと分けるために、私の場合は「devise」で生成してますが。

$ rails g devise:views Model

※生成されるViewファイル
 app/views/users/confirmations/new.html.erb
 app/views/users/mailer/confirmation_instructions.html.erb
 app/views/users/mailer/password_change.html.erb
 app/views/users/mailer/reset_password_instructions.html.erb
 app/views/users/mailer/unlock_instructions.html.erb
 app/views/users/passwords/edit.html.erb
 app/views/users/passwords/new.html.erb
 app/views/users/registrations/edit.html.erb
 app/views/users/registrations/new.html.erb
 app/views/users/sessions/new.html.erb
 app/views/users/shared/_links.html.erb
 app/views/users/unlocks/new.html.erb

 

 5-1. HTML/HAMLの記述

「registrations/new.html.erb」をユーザー新規登録ページとして編集していきます。
また、以下の通り、HTMLからHAMLに変換して記述してます。

app/views/users/registrations/new.haml.erb
.signup__main
    .signup__main__content
      %h2.signup__main__content__head
        会員情報入力

      = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|

     # 〜省略〜

              .field
                .field-label 
                  = f.label 'ニックネーム'
                %span.form-require 必須
                .field-input
                  = f.text_field :nickname, class: "field-input-full", autofocus: true, autocomplete: "nickname", placeholder: "例)メルカリ太郎"
                .input-error
                  = resource.errors.full_messages_for(:nickname)[0]

              .field
                .field-label
                  = f.label 'メールアドレス'
                %span.form-require 必須
                .field-input
                  = f.email_field :email, class: "field-input-full", autofocus: true, autocomplete: "email", placeholder: "PC・携帯どちらでも可"
                .input-error
                  = resource.errors.full_messages_for(:email)[0]

              .field
                .field-label 
                  = f.label :password, 'パスワード'
                  %span.form-require 必須
                .field-input.toggle
                  = f.password_field :password, class: "field-input-full", autocomplete: "off", autocomplete: "password", placeholder: "7文字以上の半角英数字", id:'password'
                  .checkbox-field
                    %input#js-passcheck.checkbox.js-password-toggle{type: "checkbox"}
                    %label.btn-label.js-password-label{for: "js-passcheck"}
                      %i.fas.fa-eye-slash{style: "font-size:20px;color:#808080"}
                .input-error
                  = resource.errors.full_messages_for(:password)[0]
                .field-info
                  ※ 英字と数字の両方を含めて設定してください

              .field
                .field-label 
                  = f.label '生年月日'
                %span.form-require 必須
                .birthday-select
                  = raw sprintf(f.date_select( :birthday, use_two_digit_numbers: true, prompt: "--", start_year: Time.now.year, end_year: 1900, date_separator: '%s'), '年 ', '月 ') + "日"
                .input-error
                  = resource.errors.full_messages_for(:birthday)[0]

     # 〜省略〜

              .actions
                = f.submit "登録", class: 'btn'

 

 5-2. エラー表示

今回のエラー表示は、ActiveRecordのvalidatesでエラーになった時に表示されるメッセージを利用します。
上記のように、バリデーションエラーは「errors.full_messages_for(:attribute_name)」で各attributeのエラーメッセージは取得してます。

なるほど!
ん?「各attribute」って何?

まずは何も考えずに、ネット上から「devise.ja.yml」をダウンロードし、「/config/locales」に保存してください。
次に、「devise.ja.yml」に以下を追記。
ここに追記した、「birthday、nickname、email、password」が「attribute」です。

ついでに、日本語化も実施。
「ja.yml」をネットからダウンロードし、先ほどと同じく「/config/locale/」以下に入れる。
「"%{attribute}%{message}"」とあるように、バリデーションエラーの内容に合わせて、「attribute」と「message」を選択してくれる。

config/locales/devise.ja.yml
ja:
  activerecord:
    attributes:
      user:
        birthday: "生年月日"
        nickname: "ニックネーム"
        email: Eメール
        password: パスワード

   # 〜省略〜
config/locales/ja.yml
 # 〜省略〜

   errors:
     format: "%{attribute}%{message}"
     messages:
       accepted: を受諾してください
       blank: を入力してください

 # 〜省略〜

 

 5-3. パスワードの表示/非表示

「/registrations/new.haml.erb」のパスワード入力は、何もしなければ非表示です。
何かしらのアクションを起こしたら、表示出来るようにしないと、利用者にとってわかりにくかったりします。
今回は、最近よく見る「目のマーク」を押したら表示/非表示を切り替えられるようにjavascriptとSCSSを駆使して表現しました。
参考までに一部コードを提示します。
ポイントとしては、「(password).attr('type','text');」「(password).attr('type','password');」を切り替えることで、「f.password_field」が持ってる「type」を切り替えることができ、表示/非表示を切り替えることができます。

app/views/users/registrations/new.haml.erb
     # 〜省略〜

  .field-input.toggle
    = f.password_field :password, class: "field-input-full", autocomplete: "off", autocomplete: "password", placeholder: "7文字以上の半角英数字", id:'password'
    .checkbox-field
      %input#js-passcheck.checkbox.js-password-toggle{type: "checkbox"}
      %label.btn-label.js-password-label{for: "js-passcheck"}
        %i.fas.fa-eye-slash{style: "font-size:20px;color:#808080"}

     # 〜省略〜
stylesheets/modules/users.scss
    .toggle{ 
      position: relative;
      .checkbox-field{
        position: absolute;
        right: 15px;
        top: 45px;
        .checkbox {
          display: none;
        }
      }
    }
app/assets/javascripts/registrations_new.js
$(function(){
  var password = '#password';
  var passcheck = '#js-passcheck';
  $(passcheck).change(function(){
    const passwordLabel = document.querySelector('.js-password-label');
    if ($(this).prop('checked')){
      $(password).attr('type','text');
      passwordLabel.innerHTML = '<i class="fas fa-eye" style="font-size:20px;color:#808080"></i>';
    } else {
      $(password).attr('type','password');
      passwordLabel.innerHTML = '<i class="fas fa-eye-slash" style="font-size:20px;color:#808080"></i>';
    }
  })
})

 5-4. 生年月日の選択

以下の「new.haml.erb」の通り、「date_selectタグ」という方法を使って実装しました。
超簡単です。

app/views/users/registrations/new.haml.erb
     # 〜省略〜

  .birthday-select
    = raw sprintf(f.date_select( :birthday, use_two_digit_numbers: true, prompt: "--", start_year: Time.now.year, end_year: 1900, date_separator: '%s'), '年 ', '月 ') + "日"

     # 〜省略〜

ですが1点、注意点があります。
バリデーションエラーが発生すると、「field_with_errors」クラスのdivタグが挿入され、「+ "日"」が切り離されてしまい、以下の画像のようにズレてしまいます。
Screenshot from Gyazo

【バリデーションエラー発生時】app/views/users/registrations/new.haml.erb
     # 〜省略〜

  .birthday-select
    = raw sprintf(f.date_select( :birthday, use_two_digit_numbers: true, prompt: "--", start_year: Time.now.year, end_year: 1900, date_separator: '%s'), '年 ', '月 ')
 .field_with_errors
   + "日"

     # 〜省略〜

これを回避する方法はいくつかあるようですが、今回は以下のように、「config/application.rb」に「config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag }」を追記しました。
追記後、サーバーを再起動すると、追記が反映され、Railsによる自動挿入が回避できます。

config/application.rb
   # 〜省略〜

module FleamarketAppTeamA
  class Application < Rails::Application

   # 〜省略〜

    config.action_view.field_error_proc = Proc.new { |html_tag, instance| html_tag }
  end
end


最後に

最後まで読んでいただきありがとうございます。
以上で、ユーザー新規登録についての実装は終わりです。

本記事はカスタムの部分で、大枠の作業フローと、小技的なところで参考にはなるかと思いますので、活用いただければ幸いです。
もっとわかりやすい記事、個々の機能で詳しく記載された記事はたくさんございますので、そこに至るまでのキッカケにもなればとも思っております。

今回私も、以下の記事を主に参考にさせていただきました。
作者の方々には感謝申し上げます。

量が多いため割愛させていただいておりますが、他にも参考にした記事はたくさんありますので、それら記事にも感謝です。
本記事を参考にされる方は、それらの記事も参考にしていただければと思います。

参考記事

・deviseについて
-> rails devise完全入門!結局deviseって何ができるの?
-> 【Rails】deviseの使い方を徹底解説!
-> Deviseでログイン機能をつくる [Ruby on Rails]
-> Deviseの設定手順をまとめてみた。 その1 導入編

・encrypted_password
->Gem Deviseによるパスワードの保存及び保安方法

・add_index
->データベースにindexを張る方法

・正規表現とは
-> https://www.megasoft.co.jp/mifes/seiki/about.html

・正規表現(ライブラリ)
-> https://github.com/kkos/oniguruma/blob/master/doc/RE.ja
-> http://k-takata.o.oo7.jp/mysoft/bregonig.html

・正規表現(確認ツール)
-> https://rubular.com/

・protect_from_forgery with: :exception(CSRF対策)
-> RailsのCSRF保護を詳しく調べてみた(翻訳)

・日本語化
-> https://qiita.com/kusu_tweet/items/b534c808ac1ee0382f05)

・エラー表示
-> ActiveRecordのvalidatesで表示されるエラーメッセージのフォーマットを変更する
-> 【Rails】バリデーションのエラーメッセージを取得・表示・日本語化する方法を完全解説!

・生年月日の選択
-> 【Rails】date_selectタグの使い方メモ
-> Railsのバリデーションエラーで、「field_with_errors」によるレイアウト崩れを防ぐ

辞書

・gem
 便利な機能をひとまとめにしたもの(ライブラリ)

・devise
 ユーザー新規登録/ログインといった認証機能を簡単に実装できるgemのこと

・encrypted_password
 暗号化されたパスワードを保存するカラム

・add_index
 特定のカラムからデータを取得する際に、テーブルの中の特定のカラムのデータを複製し検索が行いやすいようにする

・正規表現
 「検索」や「置換」で指定する文字列をパターン表現する方法で、プログラミング言語やテキストエディタなどで利用できる

・protect_from_forgery with: :exception
 セキュリティ対策。このコードがあると、Railsで生成されるすべてのフォームとAjaxリクエストにセキュリティトークンが自動的に含まれ、セキュリティトークンがマッチしない場合ははじかれる。ユーザー認証が完了したwebアプリのページに悪意のあるコードやリンクを仕込むCSRF(Cross-Site Request Forgery)という攻撃手法から保護出来る。

・before_action
 全てのアクションが実行される前に、この部分が実行される

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bootstrapのボタンの色を増やす方法

CDNを使っている方npmまたはyarnを利用してください。

インストール

Bootstrapをインストールしてご利用ください。

$ npm install bootstrap

Scss作成

/scss/style.scss
$qiita: #55c500;

$theme-colors: (
  "qiita": $qiita,
);

@import "../node_modules/bootstrap/scss/bootstrap.scss";

コードはこれだけです。
Gulpなどでコンパイルしてください。

確認

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Bootstrap Qiita</title>
<link rel="stylesheet" href="./css/style.css">
</head>
<body>

<button type="button" class="btn btn-primary">Primary</button>
<button type="button" class="btn btn-qiita">Qiita</button>

</body>
</html>

これで新たにbtn-qiitaが追加されていることが確認できます。
badge-qiitaも追加されてたりします。

注意点として、btn-qiita分コードが増えるので、容量も増えます。
ほどほどでどうぞ。
逆に削ることも出来るので開発、実行環境とご相談ください。

以上

npm等でインストールすると、CDNでBootstrapを利用することがなくなるぐらい便利になります。
よっぽどの理由がない限りCDNの利用は避けたほうが吉です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マルチブラウザ対策について

マルチブラウザとは

アプリケーションが様々なブラウザ上でも同じ動作をすること。
クロスブラウザともほぼ同義であり、マルチブラウザはアプリケーションの製作者が推奨するブラウザで動作することを保証しクロスブラウザは全てのブラウザで動作することを保証することを意味する。

マルチブラウザ対策の5つの方法

 1. cssハック

cssハックとは
ブラウザごとに適応させるcssを変えること

css
/* IE11のみ適応 */
@media all and (-ms-high-contrast: none) {
  *::-ms-backdrop, .selector {
    /* 適用したいスタイル */
  }
}

/* FireFoxのみ適用 */
@-moz-document url-prefix() {
    /* 適用したいスタイル */
}
/* Chrome、Safari、Operaのみ適用 */
@media screen and (-webkit-min-device-pixel-ratio:0) {
    /* 適用したいスタイル(Chrome、Safari、Operaのみ適用) */
}

 2. リセットcssの使用

リセットcssとは

ブラウザでは、デフォルトの余白やフォントの大きさを定義しているため、各ブラウザによってデザインの表示が変わってしまうことがある。
この差異を無くすためにデフォルトで適応されているcssをリセットするためのもの

リセットCSSの読み込み方は、下記の記述をhead内に記述する

html
<!DOCTYPE html>
<html lang="jp">
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="パス/reset.css">
  <title>Document</title>
</head>

 3. cssの記述法

padding, margin, borderなど要素ごとのレンダリングは、全てのブラウザで異なる。
なのでcssの記述に工夫が必要

css
/* 横幅が100pxもしくは150pxとブラウザによって表示が変わってしまう */
div{
  width:100px;
  padding-left:50px;
}

/* widthとpaddingを分けて記述すればどのブラウザでも同じ幅で表示される */
div{
  width:100px;
}
div p{
  padding-left:50px;
}

 4. jQueryの使用

javascriptでは、ブラウザ毎に記述することを意識しなければならないが、jQueryはクロスブラウザに対応しているのでブラウザの違いを特に意識する必要はない

 5. ブラウザ対応状況をチェック

HTML5、CSS3はブラウザによっては使えない場合があるので、プロパティーをブラウザ対応状況を簡単にチェックできるサービスCan I useを使う

スクリーンショット 2020-01-31 0.40.24.png

使い方は 「Can I use ______ ?」の部分に調べたい要素を入力するだけでどのブラウザで対応しているのかを調べることができる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

floatの解除: clearfixとoverflow: hiddenの詳細

clearfix

https://web-manabu.com/html-css39/

clearfixとはCSSのテクニックの一つです。クリアフィックスと呼びます。clearfixは親要素で指定します。clearfixには様々な手法がありますが、どれもfloat問題を回避するためのテクニックです。

clearfixは親要素内の最後の子要素をCSSで擬似的に作り出し、それにclear: both;を指定してfloatを解除しています。

擬似要素にheight:0;を指定しているため見た目には存在しないようになっています

overflow: hidden

https://creive.me/archives/19309/

応用的役割
ただし、これは時々本来の目的から外れた使い方もされます。例えば、floatを解除するために使われることもあります。clearfixやclearbothと同じ効果が出せるのです。そのため、overflowはfloat解除の手段として理解される方もいますが、これは本来の使い方とは違うという点は理解しおいてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む