使用 clipboard.js 复制 html.slim 中的文本

Copy text in html.slim using clipboard.js

我有一个双因素验证页面,上面显示了一个密钥(密文),我已经在我的应用程序中安装了 clipboard.js。

我想知道如何创建一个按钮来复制该密钥?

= simple_form_for @google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
  h4 = t('.step-1')
  p
    span = t('.download-app')
    span == t('.guide-link')

  h4 = t('.step-2')
  p: span = t('.scan-qr-code')

  = f.input :uri do
    = qr_tag(@google_auth.uri)

  = f.input :otp_secret do
    .input-group
      = f.input_field :otp_secret, class: 'upcase', readonly: true
      span.input-group-btn
        a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
          i.fa.fa-refresh

  h4 = t('.step-3')
  p: span = t('.enter-passcode')

  = f.input :otp

  hr.split
  = f.button :wrapped, t('.submit'), cancel: settings_path

= content_for :guide do
  ul.list-unstyled
    li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
      i.fa.fa-apple
      span = t('.ios')
    li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
      i.fa.fa-android
      span = t('.android')

我试过这样做,但没有成功:

a.btn.btn-default data-clipboard-action='copy' data-clipboard-target=':otp_secret'
  i.fa.fa-clipboard

在上面的示例中,它仅复制纯 otp_secret 文本。

spec\models\two_factor\app_spec.rb:

require 'spec_helper'

describe TwoFactor::App do
  let(:member) { create :member }
  let(:app) { member.app_two_factor  }

  describe "generate code" do
    subject { app }

    its(:otp_secret) { should_not be_blank }
  end

  describe '#refresh' do
    context 'inactivated' do
      it {
        orig_otp_secret = app.otp_secret.dup
        app.refresh!
        expect(app.otp_secret).not_to eq(orig_otp_secret)
      }
    end

    context 'activated' do
      subject { create :two_factor_app, activated: true }

      it {
        orig_otp_secret = subject.otp_secret.dup
        subject.refresh!
        expect(subject.otp_secret).to eq(orig_otp_secret)
      }
    end
  end

  describe 'uniq validate' do
    let(:member) { create :member }

    it "reject duplicate creation" do
      duplicate = TwoFactor.new app.attributes
      expect(duplicate).not_to be_valid
    end
  end

  describe 'self.fetch_by_type' do
    it "return nil for wrong type" do
      expect(TwoFactor.by_type(:foobar)).to be_nil
    end

    it "create new one by type" do
      expect {
        expect(app).not_to be_nil
      }.to change(TwoFactor::App, :count).by(1)
    end

    it "retrieve exist one instead of creating" do
      two_factor = member.app_two_factor
      expect(member.app_two_factor).to eq(two_factor)
    end
  end

  describe '#active!' do
    subject { member.app_two_factor }
    before { subject.active! }

    its(:activated?) { should be_true }
  end

  describe '#deactive!' do
    subject { create :two_factor_app, activated: true }
    before { subject.deactive! }

    its(:activated?) { should_not be_true }
  end


  describe '.activated' do
    before { create :member, :app_two_factor_activated }

    it "should has activated" do
      expect(TwoFactor.activated?).to be_true
    end
  end

  describe 'send_notification_mail' do
    let(:mail) { ActionMailer::Base.deliveries.last }

    describe "activated" do
      before { app.active! }

      it { expect(mail.subject).to match('Google authenticator activated') }
    end

    describe "deactived" do
      let(:member) { create :member, :app_two_factor_activated }
      before { app.deactive! }

      it { expect(mail.subject).to match('Google authenticator deactivated') }
    end
  end

end

app.rb:

class TwoFactor::App < ::TwoFactor

  def verify?
    return false if otp_secret.blank?

    rotp = ROTP::TOTP.new(otp_secret)

    if rotp.verify(otp)
      touch(:last_verify_at)
      true
    else
      errors.add :otp, :invalid
      false
    end
  end

  def uri
    totp = ROTP::TOTP.new(otp_secret)
    totp.provisioning_uri(member.email) + "&issuer=#{ENV['URL_HOST']}"
  end

  def now
    ROTP::TOTP.new(otp_secret).now
  end

  def refresh!
    return if activated?
    super
  end

  private

  def gen_code
    self.otp_secret = ROTP::Base32.random_base32
    self.refreshed_at = Time.new
  end

  def send_notification
    return if not self.activated_changed?

    if self.activated
      MemberMailer.google_auth_activated(member.id).deliver
    else
      MemberMailer.google_auth_deactivated(member.id).deliver
    end
  end

end

编辑: app\models\two_factor.rb:

class TwoFactor < ActiveRecord::Base
  belongs_to :member

  before_validation :gen_code, on: :create
  after_update :send_notification

  validates_presence_of :member, :otp_secret, :refreshed_at

  attr_accessor :otp

  SUBCLASS = ['app', 'sms', 'email', 'wechat']

  validates_uniqueness_of :type, scope: :member_id

  scope :activated, -> { where(activated: true) }
  scope :require_signin, -> { where(require_signin: 1) }

  class << self
    def by_type(type)
      return if not SUBCLASS.include?(type.to_s)

      klass = "two_factor/#{type}".camelize.constantize
      klass.find_or_create_by(type: klass.name)
    end

    def activated?
      activated.any?
    end

    def require_signin?
      require_signin.any?
    end
  end

  def verify?
    msg = "#{self.class.name}#verify? is not implemented."
    raise NotImplementedError.new(msg)
  end

  def expired?
    Time.now >= 30.minutes.since(refreshed_at)
  end

  def refresh!
    gen_code
    save
  end

  def active!
    update activated: true, last_verify_at: Time.now
  end

  def set_require_signin
    update require_signin: 1
  end

  def reset_require_signin
    update require_signin: nil
  end

  def deactive!
    update activated: false, require_signin: nil
  end

  private

  def gen_code
    msg = "#{self.class.name}#gen_code is not implemented."
    raise NotImplementedError.new(msg)
  end

  def send_notification
    msg = "#{self.class.name}#send_notification is not implemented."
    raise NotImplementedError.new(msg)
  end

end

您尝试做的似乎只是将输入字段的值(已由您拥有的其他代码填充)复制到系统剪贴板。您需要使用 javascript 来执行此操作,如果您有 jquery 这应该可以。

为了你的苗条,你需要一个 id 来定位它

a.btn.btn-default id= "copy"
  i.fa.fa-clipboard

尝试向要从中复制的输入元素添加一个 id

= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true 

现在尝试更改它,看看是否有效。

a.btn.btn-default data-clipboard-action='copy' data-clipboard-target='secret'
  i.fa.fa-clipboard

另外,在您的 javascript 中的某处,您需要使用类似这样的内容来定位剪辑事件:

new ClipboardJS('#secret');

请参阅此处示例 https://jsfiddle.net/ec3ywrzd/

然后你需要这个 javascript 来加载你的 html。但是您需要能够以密码字段为目标,在本例中我使用的是 id="secret"。我不确定您拥有的 OTP 代码是否生成了它自己的 ID 或现在,因此您可能需要检查您的 dom 以确定如何定位它以添加 ID。您可以尝试在此处添加 ID:

= f.input_field :otp_secret, class: 'upcase', id: "secret", readonly: true 

否则您将不得不使用其他查询选择器来定位它。 但是你可能根本不需要 clipboardjs。

这里有一个 basic example on jsfiddle 来测试它,您可以将任何字符串添加到输入字段。您需要将其添加到将由您的视图布局加载的 JS 文件中,即 application.js

$(document).ready(function() {
  $('#copy').click(function(){
    $('#secret').select();
    document.execCommand('copy');
    alert("copied!");
  })
})

您也可以see answers to this question

我根据朋友@lacostenycoder 的建议设法解决了。

即使在 show.html.slim 文件中也只需要更改,如下所示:

= simple_form_for @google_auth, as: 'google_auth', url: verify_google_auth_path do |f|
  h4 = t('.step-1')
  p
    span = t('.download-app')
    span == t('.guide-link')

  h4 = t('.step-2')
  p: span = t('.scan-qr-code')

  = f.input :uri do
    = qr_tag(@google_auth.uri)

  = f.input :otp_secret do
    .input-group
      .form-control.form-control-static = @google_auth.otp_secret
      .input-group
          a.btn.btn-default href="javascript:void(0)" data-clipboard-text = @google_auth.otp_secret
            i.fa.fa-clipboard
          a.btn.btn-default href='#{verify_google_auth_path(:app, refresh: true)}'
            i.fa.fa-refresh

  h4 = t('.step-3')
  p: span = t('.enter-passcode')

  = f.input :otp

  hr.split
  = f.button :wrapped, t('.submit'), cancel: settings_path

= content_for :guide do
  ul.list-unstyled
    li: a target='_blank' href='https://apps.apple.com/br/app/authy/id494168017'
      i.fa.fa-apple
      span = t('.ios')
    li: a target='_blank' href='https://play.google.com/store/apps/details?id=com.authy.authy'
      i.fa.fa-android
      span = t('.android')