Project

General

Profile

Feature #5953 » 5953-v3.patch

Mizuki ISHIKAWA, 2025-09-04 03:37

View differences:

app/assets/stylesheets/application.css
2541 2541
  padding: 0.5rem;
2542 2542
  width: calc(200px - 0.5rem * 2);
2543 2543
}
2544

  
2545
.api-key-actions {
2546
  display: flex;
2547
  justify-content: space-between;
2548
  align-items: center;
2549
}
2550

  
2551
.api-key-actions .copy-api-key-link {
2552
  padding: 4px 6px;
2553
  cursor: pointer;
2554
}
2555

  
2556
#sidebar .api-key-actions .copy-api-key-link svg {
2557
  opacity: 1;
2558
}
app/javascript/controllers/api_key_copy_controller.js
1
import { Controller } from "@hotwired/stimulus";
2

  
3
export default class extends Controller {
4
  static targets = ["apiKey"];
5

  
6
  copy(event) {
7
    event.preventDefault();
8

  
9
    const apiKeyText = this.apiKeyTarget.textContent?.trim();
10
    if (!apiKeyText) return;
11

  
12
    const svgIcon = event.target.closest('.copy-api-key-link').querySelector('svg')
13
    if (!svgIcon) return;
14

  
15
    copyToClipboard(apiKeyText).then(() => {
16
      updateSVGIcon(svgIcon, 'checked');
17
      setTimeout(() => {
18
        updateSVGIcon(svgIcon, 'copy');
19
      }, 2000);
20
    });
21
  }
22
}
app/views/my/_sidebar.html.erb
20 20

  
21 21
<% if Setting.rest_api_enabled? %>
22 22
<h4><%= l(:label_api_access_key) %></h4>
23
<div>
24
  <%= link_to l(:button_show), my_api_key_path, :remote => true %>
25
  <pre id='api-access-key' class='autoscroll'></pre>
23
<div data-controller="api-key-copy">
24
  <div class="api-key-actions">
25
    <%= link_to l(:button_show), my_api_key_path, :remote => true %>
26
    <a class="copy-api-key-link icon icon-only"
27
       title="<%= l(:button_copy) %>"
28
       aria-label="<%= l(:button_copy) %>"
29
       href="#"
30
       role="button"
31
       tabindex="0"
32
       style="display: none;"
33
       data-action="click->api-key-copy#copy">
34
      <%= sprite_icon('copy') %>
35
    </a>
36
  </div>
37
  <pre id='api-access-key' class='autoscroll' data-api-key-copy-target="apiKey"></pre>
38
  <%= javascript_tag("$('#api-access-key').hide();") %>
39
  <p>
40
  <% if @user.api_token %>
41
  <%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
42
  <% else %>
43
  <%= l(:label_missing_api_access_key) %>
44
  <% end %>
45
  (<%= link_to l(:button_reset), my_api_key_path, :method => :post %>)
46
  </p>
26 47
</div>
27
<%= javascript_tag("$('#api-access-key').hide();") %>
28
<p>
29
<% if @user.api_token %>
30
<%= l(:label_api_access_key_created_on, distance_of_time_in_words(Time.now, @user.api_token.created_on)) %>
31
<% else %>
32
<%= l(:label_missing_api_access_key) %>
33
<% end %>
34
(<%= link_to l(:button_reset), my_api_key_path, :method => :post %>)
35
</p>
36 48
<% end %>
app/views/my/show_api_key.js.erb
1 1
$('#api-access-key').html('<%= escape_javascript @user.api_key %>').toggle();
2

  
3
if ($('#api-access-key').is(':visible')) {
4
  $('.api-key-actions .copy-api-key-link').show();
5
} else {
6
  $('.api-key-actions .copy-api-key-link').hide();
7
}
test/system/api_key_copy_test.rb
1
# frozen_string_literal: true
2

  
3
require_relative '../application_system_test_case'
4

  
5
class ApiKeyCopySystemTest < ApplicationSystemTestCase
6
  def test_api_key_copy_to_clipboard
7
    with_settings :rest_api_enabled => '1' do
8
      log_user('jsmith', 'jsmith')
9

  
10
      user = User.find_by_login('jsmith')
11
      expected_value = user.api_key
12

  
13
      visit '/my/account'
14
      click_link 'Show'
15

  
16
      assert_selector '#api-access-key', visible: true
17
      assert_selector '.api-key-actions .copy-api-key-link', visible: true
18
      assert_equal expected_value, find('#api-access-key').text.strip
19

  
20
      find('.copy-api-key-link').click
21

  
22
      find('#quick-search input').set('')
23
      find('#quick-search input').send_keys([modifier_key, 'v'])
24
      assert_equal expected_value, find('#quick-search input').value
25
    end
26
  end
27

  
28
  private
29

  
30
  def modifier_key
31
    modifier = osx? ? 'command' : 'control'
32
    modifier.to_sym
33
  end
34
end
(5-5/5)