utamaro’s blog

誰かの役に立つ情報を発信するブログ

59.DjangoのでTwythonを使ったOAuthのサンプルを読んでみた。

参考にしたプロジェクトは↓です。

https://github.com/ryanmcgrath/twython-django

Twythonを使ってTwitterAPIを利用することができるようです。

An example Django application to showcase how to use OAuth with Twitter in Django using Twython.

と書かれていたので、OAuthの実装がわかるかな?と思って読んでみようと思いました。

それから、自分で実装できるようになると応用が効くと考えたからです。googleの認証にも使えそうですし。

requirements.txtがリポジトリ内に見当たらなかったので不満です。

なんで用意されていないのでしょうか。

setup.pyを見てみると↓のように書かれていました。

install_requires=['twython>=3.1.0', 'django'],

小さなプロジェクトというこのがわかります。

ちなみに、twythonのリンクは↓です。

https://twython.readthedocs.io/en/latest/usage/install.html

views.py

クラスベースの書き方ではないのですが、どうなのでしょうか。

良いか悪いかわかりませんが、こういうやり方もあるっていうのはドキュメントを読んで理解しているので良しとします。

https://github.com/ryanmcgrath/twython-django/blob/master/twython_django_oauth/views.py

OAuthの開始時に必要なcallbackや、認証後のurlを設定している箇所です。

    # Request an authorization url to send the user to...
    callback_url = request.build_absolute_uri(reverse('twython_django_oauth.views.thanks'))
    auth_props = twitter.get_authentication_tokens(callback_url)

    # Then send them over there, durh.
    request.session['request_token'] = auth_props

    request.session['next_url'] = request.GET.get('next',None)
    
    return HttpResponseRedirect(auth_props['auth_url'])

認証後にTwitterApiを使うところまで

コードを読んだ感じだと↓の順番で処理をすると良いみたいです。

  1. sessionからoauth_token, oauth_token_secretを取得する。
  2. twythonを使ってTwitterAPIを利用するための準備をする
  3. authorized_tokenをtwitterAPIを使って取得する
    oauth_token = request.session['request_token']['oauth_token']
    oauth_token_secret = request.session['request_token']['oauth_token_secret']
    twitter = Twython(settings.TWITTER_KEY, settings.TWITTER_SECRET,
                      oauth_token, oauth_token_secret)

    # Retrieve the tokens we want...
    authorized_tokens = twitter.get_authorized_tokens(request.GET['oauth_verifier'])
  1. authorized_tokenからscreen_nameを取得して、テーブル上にユーザーが登録されているか確認する
    1. 登録されている場合はuserオブジェクトを取得する
    2. 登録されていないなら、データを追加する
try:
    user = User.objects.get(username=authorized_tokens['screen_name'])
except User.DoesNotExist:
    # We mock a creation here; no email, password is just the token, etc.
    user = User.objects.create_user(authorized_tokens['screen_name'], "fjdsfn@jfndjfn.com", authorized_tokens['oauth_token_secret'])

DBに保存するのはoauth_token_secretで良いみたいです。

social-auth-app-djangoを使ったときは以下のデータがDB上に保存されます。

(一部マスキングしています)

{
    "auth_time": 1542545735,
    "id": 97379597xxxxxxxx620, 
    "access_token": {
        "oauth_token": "97379597xxxxxxxx620-Kn5iKEyyyyyyybzfc1QuFmyKt",
        "oauth_token_secret": "0TBySf6hjiSKqHnog4Qm6xZyyyyyyyyykmDaaos3CPxkLcn", 
        "user_id": "kdkdiem9597xxxxxxxx620", 
        "screen_name": "xxxxxxxxx"
    }
}

サンプルではoauth_tokenは保存していなかったので、どちらが正なのか判断できません。

Twythonのインスタンスを作成する際にoauth_tokenが必要かなと思うのですが、、、

ログイン時のsessionに含まれていると考えると必須ではないのかもしれません。

もしかすると、↓のコードでsaveとあるので、oauth_tokenが保存されているのかもしれません。(動かしていないのでわからないです)

profile = TwitterProfile()
profile.user = user
profile.oauth_token = authorized_tokens['oauth_token']
profile.oauth_secret = authorized_tokens['oauth_token_secret']
profile.save()

タイムラインを表示する

タイムラインを表示します。

def user_timeline(request):
    """An example view with Twython/OAuth hooks/calls to fetch data about the user in question."""
    user = request.user.twitterprofile
    twitter = Twython(settings.TWITTER_KEY, settings.TWITTER_SECRET,
                      user.oauth_token, user.oauth_secret)
    user_tweets = twitter.get_home_timeline()
    return render_to_response('tweets.html', {'tweets': user_tweets})

でもこれって、ログインしてない場合どうなるのでしょうか。

request.user.twitterprofileがNoneになるとエラーになるのですが、、、

ちなみに、htmlはこのようになっていました。

{% for tweet in tweets %}
    {{ tweet.text }}
{% endfor %}

Djangoのサンプルを覗いて使い方を勉強してみたときのメモ

django-realworld-example-appを参考にする

このプロジェクトは一番最初に参考にしたものです。

Djangoのバージョンが1.10.0と古い(記事を書いている段階での最新は2.1.5が最新)です。

https://github.com/gothinkster/django-realworld-example-app

このプロジェクトで学べること

  • ForeignKeyフィールドの作り方
author = models.ForeignKey(
    'profiles.Profile', on_delete=models.CASCADE, related_name='articles'
)
  • serializerクラスの使い方
    • method_nameというのを知りました。
    • SerializerMethodFieldを使ったget_xxxの方法を知りました。
updatedAt = serializers.SerializerMethodField(method_name='get_updated_at')
favorited = serializers.SerializerMethodField()

def get_favorited(self, instance):
    request = self.context.get('request', None)

    if request is None:
        return False

    if not request.user.is_authenticated():
        return False

    return request.user.profile.has_favorited(instance)

# ↓が実行されます
# def has_favorited(self, article):
    # """Returns True if we have favorited `article`; else False."""
    # return self.favorites.filter(pk=article.pk).exists()
  • models.pyの作り方
    • 多対多のリレーションの書き方. xxx_by
    • orderingを指定したデフォルトのソード方法
favorites = models.ManyToManyField(
    'articles.Article',
    related_name='favorited_by'
)
ordering = ['-created_at', '-updated_at']
  • urls.pyの階層構造
url(r'^api/', include('conduit.apps.profiles.urls', namespace='profiles')),
  • jsonレスポンスの拡張方法
    • ページネーションを想定したレスポンス
class ConduitJSONRenderer(JSONRenderer):
    charset = 'utf-8'
    object_label = 'object'
    pagination_object_label = 'objects'
    pagination_object_count = 'count'

    def render(self, data, media_type=None, renderer_context=None):
        if data.get('results', None) is not None:
            return json.dumps({
                self.pagination_object_label: data['results'],
                self.pagination_count_label: data['count']
            })

        # If the view throws an error (such as the user can't be authenticated
        # or something similar), `data` will contain an `errors` key. We want
        # the default JSONRenderer to handle rendering errors, so we need to
        # check for this case.
        elif data.get('errors', None) is not None:
            return super(ConduitJSONRenderer, self).render(data)

        else:
            return json.dumps({
                self.object_label: data
            })

Djangoの管理画面でBootStrap臭がひどいので消すことにした。

管理画面のBootStrap臭がひどいので消すことにしました。

アドミン機能はとても便利です。

DB内のデータをモデルを使ってCRUD操作できるので、編集が簡単です。

でも、個人的にはDB内のデータを操作する際は、専用のクライアントツールを使いたい派です。

postgresqlならPSequelを使うし、mysqlならworkbenchを使っています。

このどちらも管理画面を使用するよりも便利なので、あまり管理画面を使用しないのです。

あと、リリースしたあとに/adminを開いたときの残念感が嫌いです。

(urls.pyを修正すれば分からなくできるかもしれませんが、、、)

Bootstrap臭がすごいですよね、あのページ。あれはいらないです。

初期設定から管理画面に関する箇所を消す

settings.pyを編集します。

このページを見てみると、django.contrib.adminが管理画面に関するappというのがわかります。

https://docs.djangoproject.com/es/2.0/ref/contrib/admin/

また、管理画面は4つほど依存性を持っているようです。

この4つは管理画面で使用されているぐらいなので、アプリケーション開発に役立つ機能があると予想できます。

(実際使えます。)

というわけで、↓が修正するpythonファイルです。

INSTALLED_APPS = [
    # 'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
# from django.contrib import admin
from django.urls import path

urlpatterns = [
    # path('admin/', admin.site.urls),
]

migrateを実行します。

django.contrib.adminをコメントアウトしてmigrateを実行した結果です。

                       List of relations
 Schema |               Name                |   Type   | Owner
--------+-----------------------------------+----------+--------
 public | auth_group                        | table    | xxxxxx
 public | auth_group_id_seq                 | sequence | xxxxxx
 public | auth_group_permissions            | table    | xxxxxx
 public | auth_group_permissions_id_seq     | sequence | xxxxxx
 public | auth_permission                   | table    | xxxxxx
 public | auth_permission_id_seq            | sequence | xxxxxx
 public | auth_user                         | table    | xxxxxx
 public | auth_user_groups                  | table    | xxxxxx
 public | auth_user_groups_id_seq           | sequence | xxxxxx
 public | auth_user_id_seq                  | sequence | xxxxxx
 public | auth_user_user_permissions        | table    | xxxxxx
 public | auth_user_user_permissions_id_seq | sequence | xxxxxx
 public | django_content_type               | table    | xxxxxx
 public | django_content_type_id_seq        | sequence | xxxxxx
 public | django_migrations                 | table    | xxxxxx
 public | django_migrations_id_seq          | sequence | xxxxxx
 public | django_session                    | table    | xxxxxx

django.contrib.adminをコメントアウトしないでmigrateした結果です。

django_admin_logが消えているのが確認できます。

というかこれぐらいしか消えません。

                       List of relations
 Schema |               Name                |   Type   | Owner
--------+-----------------------------------+----------+--------
 public | auth_group                        | table    | xxxxxx
 public | auth_group_id_seq                 | sequence | xxxxxx
 public | auth_group_permissions            | table    | xxxxxx
 public | auth_group_permissions_id_seq     | sequence | xxxxxx
 public | auth_permission                   | table    | xxxxxx
 public | auth_permission_id_seq            | sequence | xxxxxx
 public | auth_user                         | table    | xxxxxx
 public | auth_user_groups                  | table    | xxxxxx
 public | auth_user_groups_id_seq           | sequence | xxxxxx
 public | auth_user_id_seq                  | sequence | xxxxxx
 public | auth_user_user_permissions        | table    | xxxxxx
 public | auth_user_user_permissions_id_seq | sequence | xxxxxx
 public | django_admin_log                  | table    | xxxxxx
 public | django_admin_log_id_seq           | sequence | xxxxxx
 public | django_content_type               | table    | xxxxxx
 public | django_content_type_id_seq        | sequence | xxxxxx
 public | django_migrations                 | table    | xxxxxx
 public | django_migrations_id_seq          | sequence | xxxxxx
 public | django_session                    | table    | xxxxxx

admin以外はすべて便利に使えるものなので、残すのが良いと思いました。

Javaの個人的読みやすいコードフォーマット

個人的読みやすいコードフォーマット【Java版】

このサイトがわかりやすく、見やすいかと思いました。

future-architect.github.io

↑のページにかかれていること以外で、自分なりに気をつけていることをまとめます。

変数の定義時には型を省略しない

int val1 = 1,
    val2 = 2;

ではなく、型を付けて宣言する。

int val1 = 1;
int val2 = 2;

ifの括弧と中括弧について

if(val==1)
{
    //
}
else
{
    //
}

ではなく、↓のように書く

if (val == 1) {
    // 
} else {
    //
}

for

for(int i=0; i<2; i++)
{
    //
}

ではなく、↓のように書く。(=,<等の前後にスペースを入れています)

for (int i = 0; i < 2; i++) {
    //
}

builder

SampleModel model = new SampleModel.builder()
    .val1(1)
    .val2(2)
    .build();

SampleModel model = new SampleModel.builder()でbuilderを使ってインスタンスを生成することを表す。

↓の例のように、builder()で改行すると、SampleModel model = new SampleModelで何をするのか分かりづらい。 (次の行を読んで、builderと初めて分かるため)

SampleModel model = new SampleModel
    .builder()
    .val1(1)
    .build();

コメントの内容について

コメントは、その処理で何をやっているかではなく、どのような処理をしているのかを書く。

if (val == 1) {
    // valが1の場合の処理
}

ではなく、↓のように書く。

if (val == 1) {
    // valが1の場合、〇〇の処理を行う
}

if (!result) {
    // 前処理で〇〇かXXの処理が失敗している場合
}

このようにすれば、コメント部分をヒントに後続処理を読むことができる。

1行に書きすぎない

sampleModel.setVal(Integer.parseInt(sameModel.getVal()));
Integer sameModelVal = Integer.parseInt(sameModel.getVal());
sampleModel.setVal(sameModelVal);

デバッグする場合、2つの処理に分けたほうがやりやすいと考えているからです。

空行はインデントを入れる

if (true) {
    int a = 1;
    // このような空行にはインデントを入れる.
    int b = 2;
}
class Sample() {
    public void method1() {
        //
    }
    // こことか
    public void method2() {
        //
    }
}

インデントを入れなかった場合は、何か処理を入れようとした場合にタブを打つことになる。

とても面倒というわけでも無いけど、コーディング時に気をつけれるところなので気をつける。

タブはスペース4にする

タブでもいいんじゃない?と思う人がいると思うけど、環境によってタブがスペースに変えられたり、タブ1がスペース8で表示されることがある。

そうすると、とても読みづらい。

かなり読みづらい。

gitのcommit時に無駄なdiffが発生したりもする。

スペース2にすると、インデントでの区切りが分かりづらく、どこからどこまでがforのブロックなのかなど分かりづらい。

個人的にはスペース4が良いと考えています。

同じ行にコメントを書く場合

int val = 1;  // hoge

スペース2つ開けて//を入れる。

そのあとにスペース1つ入れて、コメント本文を書く。

これはpythonのコメント方法を参考にしました。

省略名を使う場合は必ずコメントを入れる

int mkdir = "mkdir";  // make directory

/** name converter. nをXXに変換する  
 * @param nm 
 *  n: name. 名前
*/
public void nmConv(String n) {
    //
}

省略しなければならない場合は無いと思いますが、省略する場合は必ずコメントを入れる。

後で見たときや、別の人が読んだときに必ずわからなくなるから。

どのように利用するフィールドなのかコメントする

例えば↓のようなModelクラスでidnumberというフィールドがあるとします。

class SampleModel() {
    private String id;
    private String number;
}

このクラスだけではidとnumberがどのように使われるのかわかりません。

つまり、このクラスを使用してgetId()と書くのか、getNumber()と書くのかわからないのです。

ですが、それぞれにコメントがついていれば判断ができるというわけです。

youtubeのパルクールオススメ動画まとめ

パルクールについて簡単に

パルクールについてはこちらのページを参考にするとよいと思います。

詳しく書かれているので分かりやすいかと思います。

パルクールとは | 日本パルクール協会 | Japan Parkour Association

簡単にまとめると、↓です。

  • パルクールとは、走る・跳ぶ・登るといった移動に重点を置く動作を通じて、心身を鍛えるスポーツ
  • 機能性、体力、バランス、空間認識力、敏捷性、コーディネーション能力、正確さ、コントロール、創造的視点などを鍛える
  • パルクールの実践を続けていくことで、実践者はどのような環境においても自由に、かつ機能的に動くことのできる心身を得る

YouTubeで公開されているパルクールの動画をまとめました。

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

ヴィークルエンドという電撃文庫の小説があります。

この小説はパルクールを題材にしている小説で個人的にオススメの小説でもあります。

a.r10.to

小説家になろう掲載中の恋愛系web小説まとめ

 

小説家になろうで掲載されている小説のうち、私が読んだ恋愛系(異世界、現実)の小説をまとめています。

また、楽天Booksで発売されているもののみ、リンクを載せています。

タイトルのリンク(楽天のものではないです)をクリックすると、小説家になろうに飛びます。

 

恋愛系

婚約破棄から始まる悪役令嬢の監獄スローライフ【Web版】

これまでにない設定の小説です。令嬢が自分から牢に入ります。意味が分かりません。

令嬢に振り回される周りと、その際の掛け合いが面白いです。

婚約破棄から始まる悪役令嬢の監獄スローライフ上[山崎響] 婚約破棄から始まる悪役令嬢の監獄スローライフ 上 山崎 響 楽天Books

 

毒舌少女のために帰宅部辞めました

一気に読んでしまいました。話のテンポが良かったり、主人公の冗談がちょい笑えたり、ヒロインが主人公とかかわるうちにどんどんと変わっていき、最後はとても感動しました。

文句なしにオススメの小説です。

毒舌少女のために帰宅部辞めました(角川スニーカー文庫)[水埜アテルイ] 毒舌少女のために帰宅部辞めました (角川スニーカー文庫) 水埜 アテルイ 楽天Books

 

夢見る男子は現実主義者

設定に共感できた作品です。台詞が少し多めで頭で考えて読む作品ではありませんでした。前半はオススメ、後半はイマイチ。

 

公爵令嬢の嗜み

コミカライズされている人気の小説です。

婚約破棄から幽閉コースの運命だったのですが、あがいた結果領主となります。

恋愛要素は少ないです。文がしっかりとしていてとても読みやすいです。私が一番印象に残っているのは協会との戦いの場面です。一番盛り上がったのではないでしょうか。

楽天のリンクはコミックのリンクを載せます。

公爵令嬢の嗜み(1)(仮)[梅宮スキ] 公爵令嬢の嗜み (1) (カドカワコミックス・エース) 梅宮 スキ 楽天Books

↑から無料漫画へジャンプできます。

 

お隣の天使様にいつの間にか駄目人間にされていた件

駄目人間にされていた件と言うからには付き合う、からの結婚まで行くのかな?と予想してますが、どうなることやら。天使様と駄目人間(将来的に)の気持ちの変化が良い小説だと思います。

 

ある朝ツン過ぎる妹が隣で寝ていて急にデレ始めたので、幼馴染と後輩に相談したら修羅場になったんだけど!?~こんなハーレム嬉しくない!~

初めは長文タイトルなので避けていました。なぞのドリンクを飲むと女のこの性格が変わる!っていう設定が面白いです。性格が変わる前と、変わった後の対比が良い。主人公が逃げ回る系なのでイライラする人もいるかも。

 

お隣さんと始める節約生活。電気代のために一緒の部屋で過ごしませんか?

節約生活から始まる恋愛です。かかわりが増えて、距離が近づいていきます。鈍感系主人公と言うのでしょうか。異性として意識させたいヒロインvs異性として意識しつつも友達として信頼してくれている(と勘違いしている)主人公の駆け引きです。

 

空き巣犯のポンコツ巨乳お姉さんが口封じをしてくるせいで俺の貞操と命が危ないんだが

謎の空き巣お姉さんと、引きこもり少年の小説です。恋愛につながるのかは判断できません。面白いなと思ったのは初めの数話でした。ポンコツお姉さん可愛いです。

 

嘘の嫌いな女と嘘のつけない男

嘘を見抜くヒロインと、嘘をつけない主人公の恋愛小説です。それぞれに理由があります。ネガティブな心情が一人称で語られているので気分が少し落ち込む小説だと思います。個人的には苦手な部類です。

 

カメラの外で動いたら。

設定が面白いなと思って読み始めた小説です。

ギャルゲーの主人公をさりげなく助ける脇役ポジションの彼が、脇役を辞めたらどうなるのか。読み進めて行くと主人公ポジションのクズさが出てきます。なぜヒロインは主人公ポジションに恋をしているのかが分かってきて、それに対して脇役ポジションがどのように動くか予想しつつ読むと面白いと思います。

 

断食系男子、悟りを開く

キスもそれ以上も一切なしで交際している主人公が、寺で修行して悟りを開く話です。正直笑えました。

ここまでやるかって感じです。結跏趺坐する彼氏って女性的にはどうなんでしょうか?

「息吹をね……感じてたんだ」とか言い出す主人公になります。

 

優等生の幼なじみは私を狙う異常者でした。(R15版)

ドシリアスな作品で、苦手な人は多いと思います。個人的には好きな小説です。

似ている設定で言うと、悪の教典が近いと思います。優等生の仮面を外した主人公を拒絶するヒロインですが、主人公のことを理解していくうちにヒロインの気持ちに変化が起きてきます。

引き込まれる小説でした。

 

友人キャラの俺がモテまくるわけないだろ?

学校中の(一部除く)生徒に避けられている主人公がメインの小説です。

ヒロインごとに何かしらの問題を抱えていて、主人公がそれを解決していくことで恋愛に発展していきます。

第一章と第三章が面白いと思いました。イケメンの妹が抱えている問題に共感できたし、クール系泥酔ポンコツ先生が可愛いし。

 

偽の恋人だったクラスの美少女たちが、本当に俺のことを好きになっていた件

32話で読むのを止めてしまいました。

台詞が多めですらすら読めるのですが、心理描写が少ないので共感しづらいかったのかと思います。

 

そして僕は聖女を裏切った

 寝取り系が嫌いな方は読まないほうが良いかと思いました。

勇者がもっともらしい理由をつけて聖女を主人公から奪った形に見えてしまいました。でも、聖女が主人公に嫌な気持ちになって欲しくないと思うのも分かります。

キャラに対して胸糞でしたが、自分が同じ立場と想定して場合どうするか考えると良い選択が出てこないのです。一度読んで自分ならどうするか考えると面白い小説だと思います。

 

そして少女は悪女の体を手に入れる

36話で読むのを止めてしまいました。

地の文が主人公の感想になっているのが原因でした。

ベットから起き上がるのにもひーひー言っていた私がジャンプ……あ、涙が。」とか入っているのでイマイチでした。

 

エリスの聖杯

一気に読める小説でオススメです。魅力的なキャラクターが多く、特にスカーレットとコニーのやり取りが面白いし、陰謀を解き明かしていく流れが良い。

文句なしで面白い小説だと思います。

 

落ちぶれ才女の幸福

個人的に好きな小説で、既に何度も読んでいる小説です。

読み始めると一気に読んでしまうと思います。

うまく感想がかけないのがもどかしいですが、読んで損はないと思います。

ただの『セリア』を受け入れてくれる――それだけで、私は幸せなの」という台詞がグッときました。

 

 妹フレグランス

 タイトルから想像できないダークラブコメでした。今は更新が止まっています。

 主人公の事が好きな妹と、主人公の事が好きな3人のヒロインの話です。

現在は3人のうち2人の話が終わっています。

オススメの小説です。妹vsヒロイン

 「お前たちのそれは本気とは言わないんだよ」という台詞が刺さります。妨害を受けてあきらめる、引く程度なら本気ではないということでしょうか。

栞編まで読むと妹の考えが分かってきます。

 

ヤンデレの執着(ソレ)は果たして愛なのか?

男性側が病んでる小説です。

主人公が割りと酷い扱いを受けているのですが、それでもなぜかクスッと笑えてしまう小説でした。オススメです。

 

 乙女ゲームの破滅フラグしかない悪役令嬢に転生してしまった…

 主人公のやることなすことがことごとく裏目にでる話です。

破滅エンドを回避しようとして、なぜか好感を持たれて、、、という流れですね。

斜め上な努力が可愛く、シリアス成分が無いのでとても読みやすいです。

コミックスですが、楽天のリンクを載せます。

乙女ゲームの破滅フラグしかない悪役令嬢に転生してしまった…[ひだかなみ] 乙女ゲームの破滅フラグしかない悪役令嬢に転生してしまった・・・(1) (IDコミックス ZERO-SUMコミックス) ひだかなみ 楽天市場

 

転生不幸~異世界孤児は成り上がる~

孤児から始まって、徐々に成り上がる話です。成り上がるといっても王妃になるとかではないです。

きれいにまとめられているので読みやすい小説だと思います。

オススメです。

転生不幸(1)[日生] 転生不幸(1) 異世界孤児は成り上がる (アリアンローズ) 日生 楽天Books

 

 

 

 

textarea内でtabを使えるようにするためのまとめ

textareaでtabを使えるようにするために参考にしたサイト

Qiitaの記事なのですが、うまく動かなかったと思います。

https://qiita.com/laineus/items/12a220d2ab086931232d

tabを押すと、文字の前にTAB_STRが挿入されます。

自分が望んでいたのは、tabを押すと、tabが挿入されるものです。

demo画面を参考にして、少し書き直してみました。

もう少しきれいに書ける気がしますが、時間と気力が続かなくてかけませんでした。

let textareaTab = {};
(function() {
    textareaTab.setEvent = function() {
        console.log("set textarea tab event.");
        var textareas = document.querySelectorAll('textarea');
        for (var ti = 0; ti < textareas.length; ti++) {
            textareas[ti].addEventListener('keydown', function(e) {
                if (e.keyCode === 9) {
                    e.preventDefault();
                    var isShift = e.shiftKey;
                    var elm = e.target;
                    var txt = elm.value;
                    var slct = {
                        left: elm.selectionStart,
                        right: elm.selectionEnd,
                    };
                    if (slct.left == slct.right && !isShift) {
                        elm.value = txt.substr(0, slct.left) + '\t' + txt.substr(slct.left, txt.length);
                        slct.left++;
                        slct.right++;
                    } else {
                        var lineStart = txt.substr(0, slct.left).split('\n').length - 1;
                        var lineEnd = txt.substr(0, slct.right).split('\n').length - 1;
                        var lines = txt.split('\n');
                        for (var i = lineStart; i <= lineEnd; i++) {
                            if (!isShift) {
                                lines[i] = '\t' + lines[i];
                                if (i == lineStart) {
                                    slct.left++;
                                }
                                slct.right++;
                            } else if (lines[i].substr(0, 1) == '\t') {
                                lines[i] = lines[i].substr(1);
                                if(i == lineStart) {
                                    slct.left--;
                                }
                                slct.right--;
                            }
                        }
                        elm.value = lines.join('\n');
                    }
                    elm.setSelectionRange(slct.left, slct.right);
                    return false;
                }
            })
        }
    }
})();

export default textareaTab;

exportしているので、別のjsからimportできます。

↓のように使います。

import textareaTab from './textarea_tab.js';