JavaScriptで日本語入力の文字数制限を行いたい
テキストエリアの入力文字数制限をしたいとき、change eventを拾って入力値をチェックするだけだと、変換を伴う文字入力の際に期待した動きにならなった。
環境
jsなら何でもいいんですが、今回私はreactを使ったのでreact versionを記載します。
Service | Version |
---|---|
react | 18.2.0 |
上手く動かなかったコード
import React, { memo, useCallback, useState, type ReactElement } from 'react'; const TEXT_LEN = 5; function Hoge(): ReactElement { const [text, setText] = useState<string>(''); const handleChangeText = useCallback((e: React.ChangeEvent<HTMLInputElement>): void => { const text = e.target.value; setText((prevText: string): string => text.substring(0, TEXT_LEN)); }, []); return ( <input name="text" type="text" value={text} onChange={handleChangeText} /> ); }; export default memo(Hoge);
環境によって挙動が違う。 他のOSやブラウザ、バージョンによっても違うかもしれないが、いずれにしても思った挙動にならないので調査は以下のみ。
OS | Version | ブラウザ | 挙動 |
---|---|---|---|
Windows | 10 | Chrome | テキスト編集システムを使って指定文字数以上を入力すると、既に入力されている文字を消しながら入力される。 |
Mac | ventura | 13.2.1 | 入力可能文字数内のひらがなのみ入力され、変換を確定しても無視される。 |
iOS | 16.3.1 | Safari | 入力可能文字数内のひらがなのみ入力され、変換を確定しても無視される。 |
Android | 12 | Chrome | 変換途中でも文字数が肥えた時点で文字入力が確定され、入力できない。 |
原因
IME等のようなテキスト編集システムが原因。
対応
composition{start|update|end}
でテキスト編集システムの編集セッションイベントが取れる。
これを使えばどうにかできるのではと考え、onCompositionEnd
で入力を確定させる対応を行ったが、onChange
での文字入力も同時に行わないと思った通りの挙動にならなかった。
最終的には以下の形に落ち着いた。
import React, { memo, useCallback, useRef, useState, type ReactElement } from 'react'; const TEXT_LEN = 5; function Hoge(): ReactElement { const [text, setText] = useState<string>(''); const isCompositionStart = useRef<boolean>(false); const commitStr = useCallback(() => { setText((prevText: string): string => prevText.substring(0, TEXT_LEN)); }, []); const handleCompositionStart = useCallback((): void => { isCompositionStart.current = true; }, []); const handleCompositionEnd = useCallback((): void => { isCompositionStart.current = false; commitStr(); }, []); const handleChangeText = useCallback((e: React.ChangeEvent<HTMLInputElement>): void => { setText(e.target.value); if (!isCompositionStart.current) { commitStr(); } }, [isCompositionStart.current]); return ( <input name="text" type="text" value={text} onCompositionStart={handleCompositionStart} onCompositionEnd={handleCompositionEnd} onChange={handleChangeText} /> ); }; export default memo(Hoge);
文字入力自体はいかなる状態でも受け入れる。 この対応をしないと変換前の文字で確定したり、意図した挙動とならない。
しかし文字数制限は行いたいので、文字入力後にcommitStr
で文字数制限を行う。
ただしテキスト変換システムを使っている場合は、変換確定前に文字数を削ると変換がキャンセルされたりと意図しない挙動となる。
そこでonCompositionStart
でテキスト変換中であるかを確認し、もし変換中であれば文字数確定をスキップする。
しかしこのままではテキスト変換システムを使った入力ではテキスト文字数が制限されない。
そのためisCompositionEnd
で文字数確定を実施する。
また、変換が完了しているため、テキスト変換中フラグを下げる。
所感
文字数制限について調べると、maxlength
を使ったりjsを使ったりという手法は色々出てくるが、IME等での変換が上手くいかない件に関して無視されがちだったので、備忘録をかね残しておくこととする。
もうちょいいい感じにできそうな気がするが、まあ動くしもういいかな。
GroverでHTMLをPDFに変換したら日本語が文字化けして豆腐になっちゃった件
環境
Service | Version |
---|---|
OS | Amazon Linux 2 |
Ruby | 3.0.2 |
Ruby on Rails | 7.0.3 |
grover | 1.1.1 |
原因
Groverで使用しているpuppeteerにて日本語化対応が必要だった。
対応
日本語フォントを入れましょう。
$ sudo yum install ipa-gothic-fonts ipa-mincho-fonts ipa-pgothic-fonts ipa-pmincho-fonts
Ruby on RailsでreCAPTCHA Enterprise を使いたい
無料版と有料版のreCAPTCHAとがありますが、使い方は一緒かと思ったら違ってちょっと大変だったお話。
環境
Service | Version |
---|---|
Ruby | 2.7.4 |
Ruby on Rails | 6.1 |
recaptcha | 5.8.1 |
前提
Gemはrecaptchaを使います。
通常のreCAPTCHAなら下記用意しておけば、後はドキュメント通りでOKです。
Recaptcha.configure do |config| config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy' config.secret_key = '6Lc6BAAAAAAAAKN3DRm6VA_xxxxxxxxxxxxxxxxx' # Uncomment the following lines if you are using the Enterprise API: # config.enterprise = true # config.enterprise_api_key = 'AIzvFyE3TU-g4K_Kozr9F1smEzZSGBVOfLKyupA' # config.enterprise_project_id = 'my-project' end
でもenterpriseにはsecret_keyがありません。
代わりにenterprise_api_keyを使います。
ドキュメントにはコメントを外しましょうとはありますが、secret_keyは不要ですとは無かったのでsecret_keyを探して右往左往してしまいました。。
というわけでenterpriseでは以下になります。
Recaptcha.configure do |config| config.site_key = '6Lc6BAAAAAAAAChqRbQZcn_yyyyyyyyyyyyyyyyy' config.enterprise = true config.enterprise_api_key = 'AIzvFyE3TU-xxxxxxxxxxxxxxxxxxxxxxxxxxxx' config.enterprise_project_id = 'my-project' end
各キーの場所
ついでなのでキーのそれぞれの場所も記しておきます。
これまた探すのに手間取って右往左往しましたので。。。
site_key
GCPの「セキュリティ」から「reCAPTCHAT Enterprise」を選択し、キーを作成してください。
enterprise_api_key
GCPの「APIとサービス」からAPIキーを発行してください。
キーの制限としては「reCAPTCHA Enterprise API」を選択しておいてください。
enterprise_project_id
ダッシュボードの「プロジェクト情報」に記載のプロジェクトIDです。
所感
折角recaptchaにEnterprise用の機能まで備わっているのに、分かりづらくてもったいない。。
公式は公式で google-cloud-recaptcha_enterprise を押してきていて、やたらと混乱してしまった。。。
Devise OmniAuthでサービス先へリダイレクトする前にごにょごにょしたい
OmniAuth、ほとんど何もしなくてもOAuthを実装出来て便利ですよね。
でもサービス側へリダイレクトする直前にごにょごにょしたいことってありませんか?
ありませんか。そうですよね、普通は。。
まあ今回は普通じゃなかったんです。リダイレクト直前にちょっとSessionに色々小細工をしたかったんですが、OmniauthCallbacksController#passthru
をオーバーライドしてもうまくいかなかったんで、その方法を共有します。
環境
Service | Version |
---|---|
Ruby | 3.0.2 |
Ruby on Rails | 6.1 |
devise | 4.8.0 |
omniauth | 2.0.4 |
omniauth-google-oauth2 | 1.0.0 |
omniauth-rails_csrf_protection | 1.0.0 |
対応
最初に書いた通り、リダイレクト寸前にごにょごにょしようとpassthruをオーバーライドしたんですが上手くいかず、それもそのはずコントローラーには到達していなかったんですね。
でもご安心ください。OmniAuthは大変によく完成されたgemです。そういったときのためのメソッドが用意されていました。
今回はリダイレクト時に追加されたparameterをsessionに格納してみましょう。
link_to :OAuth, user_xxxxxx_omniauth_authorize_path(hoge: :fuga)
config/initializers/omniauth.rb
OmniAuth.configure do |config| config.before_request_phase = ->(env) { request = ActionDispatch::Request.new(env.dup) request.session[:fuga] = request.params[:hoge] } end
所感
所感というか愚痴というか。
なぜこういう方法を調べたかというと、別の方がなんとも頓珍漢な方法でOAuthまでにごにょごにょされておりまして、それはあんまりだろうと調べてみた結果こういうメソッドが用意されていたという。。
ちっとは自分で調べるという事くらいしてほしいですね。
削除したActiveStorageへのアクセスは404にしたい
画像の更新などによりActiveStorageのBlobが削除されたにもかかわらず、URLがキャッシュされていたなどの理由により削除された画像にアクセスが来た場合、下記エラーが報告される。
ActiveRecord::RecordNotFoundactive_storage/blobs#show Couldn't find ActiveStorage::Blob with 'id'=xxx
別段この動きに不満はないが、bugsnagに大量にこのエラーが飛んできて重要なエラー通知に埋もれてしまいかねないので404にしたい。
環境
Service | Version |
---|---|
Ruby | 2.7.3 |
Ruby on Rails | 6.0.3 |
対応
ActiveStorageのcontrollerは app/controllers/active_storage/
以下の対応するcontrollerに置けばオーバーライドできるので、そちらでrescueする。
app/controllers/active_storage/base_controller.rb
# frozen_string_literal: true # The base class for all Active Storage controllers. class ActiveStorage::BaseController < ActionController::Base include ActiveStorage::SetCurrent protect_from_forgery with: :exception rescue_from ActiveRecord::RecordNotFound, with: :render_404 private def render_404 head :not_found end end
所感
オーバーライドは何かあった場合が怖いので、もっといい方法があればそちらにしたい。。。
けど app/controllers/active_storage/blobs_controller.rb
には下記のように書いているので、オーバーライドが推奨方法なのかもしれない。
Note: These URLs are publicly accessible. If you need to enforce access protection beyond the security-through-obscurity factor of the signed blob references, you'll need to implement your own authenticated redirection controller.
ActiveStorageの画像ファイルなどを、アンカー要素のdownload属性でダウンロードさせたい
ActiveStorageのservice_url
のdisposition
オプションがデフォルトで:inline
なため、画像やpdfのアンカーにdownload属性をつけていても、ブラウザ上で開いてしまう。
これをダウンロードさせたい。
環境
Service | Version |
---|---|
Ruby | 2.7.2 |
Ruby on Rails | 6.0.3.4 |
S3 | - |
対応
ダウンロードさせたいcontent_typeを config.active_storage.content_types_to_serve_as_binary
に追加してあげればいいらしい。
別にどこに書いてもいいが、今回はサクッと config/application.rb
に書いた。
config.active_storage.content_types_to_serve_as_binary += %w(image/png image/jpeg image/gif image/gif image/bmp application/pdf)
以上です。
送信サーバーと同一ドメインの受信サーバー宛にメールが送信できない
送信サーバーは Amazon Lightsail 、受信サーバーはムームーメールという環境でlLightsailからメール送信ができなくなった。
元々AWSのメール送信制限に引っかかりメールが送信できなくなっており、その調査で /etc/postfix/main.cf
はいじっていたのでその辺が怪しいだろうとネットの海を探してみたらまさしくヒット。
環境
Service | Version |
---|---|
Amazon Lightsail | - |
ムームーメール | - |
Postfix | 2.10.1 |
前提
サーバードメインを example.com
とする。
調査
調査の基本はログからと先輩方に叩き込まれたので脳死でまずはログを見る。
ログを見るとどうやら info@example.com
宛が何故か自身にメールを送ってしまっている。
/var/log/maillog
postfix/smtpd[32266]: connect from localhost[127.0.0.1] postfix/smtpd[32266]: 8A714407E92: client=localhost[127.0.0.1] postfix/cleanup[32270]: 8A714407E92: message-id=<7e603f861e7c4de87fdb9596a7b15229@example.com> opendkim[23140]: 8A714407E92: DKIM-Signature field added (s=example, d=example.com) postfix/qmgr[22693]: 8A714407E92: from=<info@example.com>, size=1489, nrcpt=1 (queue active) postfix/smtpd[32266]: disconnect from localhost[127.0.0.1] postfix/local[32271]: 8A714407E92: to=<root@example.com>, orig_to=<info@example.com>, relay=local, delay=0.18, delays=0.1/0.06/0/0.01, dsn=2.0.0, status=sent (delivered to mailbox)
正しくは以下のように mx01.muumuu-mail.com
宛に送って欲しい。
postfix/smtpd[394]: connect from localhost[127.0.0.1] postfix/smtpd[394]: A981E400056: client=localhost[127.0.0.1] postfix/cleanup[399]: A981E400056: message-id=<17a46dbb2781b457c2325a1b2db4bae7@example.com> opendkim[23140]: A981E400056: DKIM-Signature field added (s=example, d=example.com) postfix/smtpd[394]: disconnect from localhost[127.0.0.1] postfix/qmgr[391]: A981E400056: from=<info@example.com>, size=1489, nrcpt=1 (queue active) postfix/smtp[400]: A981E400056: to=<info@example.com>, relay=mx01.muumuu-mail.com[157.7.107.7]:25, delay=0.4, delays=0.11/0.03/0.04/0.21, dsn=2.0.0, status=sent (250 Queued! <17a46dbb2781b457c2325a1b2db4bae7@example.com> (Queue-Id: 115281A40582))
そして試しに送った他ドメイン宛のテストはしっかり送信される。
そこでネットの海を探索した所以下のサイトに巡り合った。本当にありがとうございます。助かりました。
つまり、mydestination はローカルで受信するドメイン名を指定するのですが、ここで下記のように $mydomain を指定していると今回のような事象になってしまいます。
あ、やったわ。メール送信制限にたどり着くまでにこの対応やったわ。。。
というわけで mydestination
から $mydomain
を削除。
/etc/postfix/main.cf
- mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain + mydestination = $myhostname, localhost.$mydomain, localhost
そしてPostfixを再起動させたら、正常に送信されるようになりました。
めでたしめでたし。