Skip to content

Commit

Permalink
Merge pull request #5588 from dodona-edu/fix/cliboard-empty
Browse files Browse the repository at this point in the history
Replace clipboardjs by webcomponent
  • Loading branch information
jorg-vr authored Jun 6, 2024
2 parents f0ed5f4 + fa1a4e4 commit 7d0bee8
Show file tree
Hide file tree
Showing 19 changed files with 75 additions and 144 deletions.
42 changes: 34 additions & 8 deletions app/assets/javascripts/components/copy_button.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,63 @@
import { html, PropertyValues, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import { initTooltips, ready } from "utilities";
import { initTooltips } from "utilities";
import { i18n } from "i18n/i18n";
import { DodonaElement } from "components/meta/dodona_element";

/**
* A button that copies the text content of a given element to the clipboard.
* Alternatively, the text can be set directly.
* The button is styled as a small icon button.
* The button is a tooltip that shows the current status of the copy operation.
*
* @element d-copy-button
*
* @property {HTMLElement} codeElement - The element whose text content is copied to the clipboard.
* @property {HTMLElement} target - The element whose text content is copied to the clipboard.
* @property {string} targetId - The id of the element whose text content is copied to the clipboard.
* @property {string} text - The text that is copied to the clipboard.
*/
@customElement("d-copy-button")
export class CopyButton extends DodonaElement {
@property({ type: String, attribute: "target-id" })
targetId: string;

_target: HTMLElement;
@property({ type: Object })
codeElement: HTMLElement;
get target(): HTMLElement {
return this._target ?? document.getElementById(this.targetId);
}

set target(value: HTMLElement) {
this._target = value;
}

_text: string;

@property({ type: String })
get text(): string {
return this._text ?? this.target?.textContent;
}

get code(): string {
return this.codeElement.textContent;
set text(value: string) {
this._text = value;
}

@property({ state: true })
status: "idle" | "success" | "error" = "idle";

async copyCode(): Promise<void> {
try {
await navigator.clipboard.writeText(this.code);
await navigator.clipboard.writeText(this.text);
this.status = "success";
} catch (err) {
window.getSelection().selectAllChildren(this.codeElement);
this.status = "error";
if (this.target) {
// Select the text in the code element so the user can copy it manually.
window.getSelection().selectAllChildren(this.target);
this.status = "error";
} else {
// rethrow the error if there is no target
throw err;
}
}
}

Expand Down
29 changes: 29 additions & 0 deletions app/assets/javascripts/components/copy_container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { html, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators.js";
import "components/copy_button";
import { DodonaElement } from "components/meta/dodona_element";

/**
* A container that holds code that can be copied to the clipboard.
*
* @element d-copy-container
*
* @property {string} content - The content that is copied to the clipboard.
*/
@customElement("d-copy-container")
export class CopyContainer extends DodonaElement {
@property({ type: String })
content: string;

@property({ state: true })
containerId: string;

constructor() {
super();
this.containerId = "copy-container-" + Math.random().toString(36).substring(7);
}

protected render(): TemplateResult {
return html`<pre class="code-wrapper"><code id="${this.containerId}">${this.content}</code><d-copy-button targetId="${this.containerId}"></d-copy-button></pre>`;
}
}
32 changes: 0 additions & 32 deletions app/assets/javascripts/copy.ts

This file was deleted.

2 changes: 1 addition & 1 deletion app/assets/javascripts/exercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ function initCodeFragments(): void {
const wrapper = codeElement.parentElement;
wrapper.classList.add("code-wrapper");
const copyButton = new CopyButton();
copyButton.codeElement = codeElement;
copyButton.target = codeElement;

render(copyButton, wrapper, { renderBefore: codeElement });
});
Expand Down
2 changes: 1 addition & 1 deletion app/assets/stylesheets/models/activities.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ center img {

code {
overflow: auto;
width: 100%;
width: calc(100%);
display: block;
}

Expand Down
11 changes: 0 additions & 11 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,17 +121,6 @@ def activatable_link_to(url, options = nil, &)
link_to(url, options, &)
end

def clipboard_button_for(selector)
selector = selector.to_s
selector.prepend('#') unless selector.starts_with?('#')
button_tag class: 'btn btn-icon',
type: 'button',
title: t('js.copy-to-clipboard'),
data: { clipboard_target: selector } do
tag.i(class: 'mdi mdi-clipboard-outline')
end
end

def markdown_unsafe(source)
source ||= ''
Kramdown::Document.new(source,
Expand Down
23 changes: 1 addition & 22 deletions app/helpers/renderers/feedback_code_renderer.rb
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
class FeedbackCodeRenderer
require 'json'
include Rails.application.routes.url_helpers

@instances = 0

class << self
attr_accessor :instances
end

def initialize(code, programming_language)
@code = code
@programming_language = programming_language
@builder = Builder::XmlMarkup.new
self.class.instances += 1
@instance = self.class.instances
end

def add_code
@builder.div(class: 'code-listing-container') do
parse
# Only display copy button when the submission is not empty
if @code.present?
# Not possible to use clipboard_button_for here since the behaviour is different.
@builder.button(class: 'btn btn-icon copy-btn', id: "copy-to-clipboard-#{@instance}", title: I18n.t('js.code.copy-to-clipboard'), 'data-bs-toggle': 'tooltip', 'data-bs-placement': 'top') do
@builder.i(class: 'mdi mdi-clipboard-outline') {}
end
end
@builder.script(type: 'application/javascript') do
@builder << <<~HEREDOC
window.dodona.ready.then(() => {
window.dodona.attachClipboard("#copy-to-clipboard-#{@instance}", #{@code.to_json});
});
HEREDOC
end
@builder.tag!('d-copy-button', text: @code) {} if @code.present?
end
self
end
Expand Down
5 changes: 1 addition & 4 deletions app/javascript/packs/application_pack.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,14 @@ import { Drawer } from "drawer";
import { Toast } from "toast";
import { Notification } from "notification";
import { checkTimeZone, checkIframe, initTooltips, ready, setHTMLExecuteScripts, replaceHTMLExecuteScripts } from "utilities.ts";
import { initClipboard } from "copy";
import { FaviconManager } from "favicon";
import { themeState } from "state/Theme";
import "components/saved_annotations/saved_annotation_list";
import "components/progress_bar";
import "components/theme_picker";
import { userState } from "state/Users";
import "components/series_icon.ts";

// Initialize clipboard.js
initClipboard();
import "components/copy_container.ts";

// Init drawer
ready.then(() => new Drawer());
Expand Down
4 changes: 0 additions & 4 deletions app/javascript/packs/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const bootstrap = { Alert, Button, Collapse, Dropdown, Modal, Popover, Tab, Tool
window.bootstrap = bootstrap;

import { initTooltips, ready, setHTMLExecuteScripts } from "utilities.ts";
import { initClipboard } from "copy";
import { themeState } from "state/Theme";

// Use a global dodona object to prevent polluting the global na
Expand All @@ -20,7 +19,4 @@ dodona.setTheme = theme => themeState.selectedTheme = theme;
dodona.setHTMLExecuteScripts = setHTMLExecuteScripts;
window.dodona = dodona;

// Initialize clipboard.js
initClipboard();

ready.then(initTooltips);
2 changes: 0 additions & 2 deletions app/javascript/packs/submission.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { initSubmissionShow, initCorrectSubmissionToNextLink, initSubmissionHistory, showLastTab } from "submission.ts";
import { initMathJax, onFrameMessage, onFrameScroll } from "exercise.ts";
import { attachClipboard } from "copy";
import { evaluationState } from "state/Evaluations";
import codeListing from "code_listing";
import { annotationState } from "state/Annotations";
Expand All @@ -9,7 +8,6 @@ import { initFileViewers } from "file_viewer";

window.dodona.initSubmissionShow = initSubmissionShow;
window.dodona.codeListing = codeListing;
window.dodona.attachClipboard = attachClipboard;
window.dodona.initMathJax = initMathJax;
window.dodona.initCorrectSubmissionToNextLink = initCorrectSubmissionToNextLink;
window.dodona.initSubmissionHistory = initSubmissionHistory;
Expand Down
6 changes: 1 addition & 5 deletions app/views/api_tokens/_show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
<div class="form-horizontal">
<div class="field form-group row">
<div class="col-md-6">
<div class="input-group ">
<% name = "fresh-token-value" %>
<%= text_field_tag name, token.token, readonly: true, class: 'form-control' %>
<%= clipboard_button_for name %>
</div>
<d-copy-container content="<%= token.token %>"></d-copy-container>
</div>
</div>
</div>
8 changes: 3 additions & 5 deletions app/views/application/_token_field.html.erb
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
<%= text_field_tag name, value, readonly: true, class: 'form-control' %>
<%= clipboard_button_for name %>
<d-copy-container content="<%= value %>"></d-copy-container>
<% if reset_url.present? %>
<%= link_to reset_url,
title: t('general.generate_token'),
method: :post,
data: {confirm: t('general.confirm_reset_token')},
remote: true,
class: 'btn btn-icon' do %>
<i class="mdi mdi-refresh mdi-18"></i>
class: 'btn btn-outline btn-icon ms-1 flex-shrink-0' do %>
<i class="mdi mdi-refresh mdi-24"></i>
<% end %>
<% end %>
2 changes: 1 addition & 1 deletion app/views/courses/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@
</div>
<div class="field form-group row">
<div class="offset-sm-3 col-sm-6">
<div class="input-group" id="hidden_show_link_field">
<div class="d-flex" id="hidden_show_link_field">
<%= render partial: 'application/token_field', formats: [:html],
locals: {
name: :hidden_show_link,
Expand Down
5 changes: 1 addition & 4 deletions app/views/judges/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,7 @@
<div class="callout callout-info">
<h4>Webhook</h4>
<p><%= t ".webhook_html" %></p>
<div class="input-group">
<%= text_field_tag :webhook_link, webhook_judge_url(@judge), readonly: true, class: 'form-control' %>
<%= clipboard_button_for :webhook_link %>
</div>
<d-copy-container content="<%= webhook_judge_url(@judge) %>"></d-copy-container>
</div>
</div>
</div>
Expand Down
5 changes: 1 addition & 4 deletions app/views/repositories/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@
<div class="callout callout-info">
<h4>Webhook</h4>
<p><%= t ".webhook_html" %></p>
<div class="input-group intput-froup-sm">
<%= text_field_tag :webhook_link, webhook_repository_url(@repository), readonly: true, class: 'form-control' %>
<%= clipboard_button_for :webhook_link %>
</div>
<d-copy-container content="<%= webhook_repository_url(@repository) %>"></d-copy-container>
</div>
</div>
<% end %>
Expand Down
2 changes: 1 addition & 1 deletion app/views/series/edit.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<div class="field form-group row">
<%= label_tag :access_token, t('.access-link'), class: 'col-sm-3 col-form-label' %>
<div class="col-sm-9">
<div class="input-group" id="access_token_field">
<div class="d-flex" id="access_token_field">
<%= render partial: 'token_field',
locals: {
name: :access_token,
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"babel-loader": "^9.1.3",
"babel-plugin-macros": "^3.1.0",
"bootstrap": "5.3.3",
"clipboard": "^2.0.11",
"codemirror-lang-prolog": "^0.1.0",
"codemirror-lang-r": "^0.1.0-2",
"core-js": "^3.37.1",
Expand Down
7 changes: 0 additions & 7 deletions test/renderers/feedback_code_renderer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,4 @@ class FeedbackCodeRendererTest < ActiveSupport::TestCase
assert_equal gen_html_orig, gen_html_cr
end
end

test 'Multiple instances result in unique html' do
programming_language = 'python'
tables = 5.times.collect { FeedbackCodeRenderer.new('print(5)', programming_language).add_code.html }

assert_equal tables.uniq, tables
end
end
Loading

0 comments on commit 7d0bee8

Please sign in to comment.