Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: 2.1

orbs:
newspack: newspack/newspack@1.4.3
newspack: newspack/newspack@1.5.6

jobs:
release_wporg:
Expand Down Expand Up @@ -51,3 +51,6 @@ workflows:
branches:
only:
- release
php:
jobs:
- newspack/test-php
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules/
*.zip
release/
vendor
.phpunit.result.cache
86 changes: 86 additions & 0 deletions assets/clipboard-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* Clipboard utility for copying text to clipboard
*
* uses Clipboard API.
*/
window.ClipboardUtils = {
/**
* Copy text to clipboard
* @param {string} text - Text to copy
* @returns {Promise<boolean>} - Promise resolving to success status
*/
async copyText(text) {
if (!navigator.clipboard) {
console.warn('Clipboard API not available');
return false;
}

try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
console.error('Failed to copy text:', err);
return false;
}
},

/**
* Get text content from an element
* @param {string|Element} elementOrSelector - Element or selector
* @returns {string} - Text content
*/
getElementText(elementOrSelector) {
let element;

if (typeof elementOrSelector === 'string') {
element = document.querySelector(elementOrSelector);
} else {
element = elementOrSelector;
}

if (!element) {
return '';
}

const tagName = element.tagName.toLowerCase();
if (tagName === 'input' || tagName === 'textarea') {
return element.value || '';
} else {
return element.textContent || element.innerText || '';
}
},

/**
* Copy content from an element
* @param {string|Element} elementOrSelector - Source element
* @param {Element} button - Button element for feedback
* @returns {Promise<boolean>} - Promise resolving to success status
*/
async copyFromElement(elementOrSelector, button) {
const text = this.getElementText(elementOrSelector);
if (!text) {
return false;
}

const success = await this.copyText(text);

if (success && button) {
this.showButtonFeedback(button);
}

return success;
},

/**
* Show temporary "Copied!" feedback on button
* @param {Element} button - Button element
*/
showButtonFeedback(button) {
const originalText = button.textContent || button.innerText;

button.textContent = 'Copied!';
setTimeout(() => {
button.textContent = originalText;
}, 2000);
}
};
117 changes: 113 additions & 4 deletions assets/republish-template.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,127 @@
font-family: monospace;
font-size: smaller;
text-align: left;
border: 2px solid #c7c7c7;
height: 50vh;
border: 1px solid #ccc;
height: 40vh;
line-height: 1.6em;
overflow: scroll;
padding: 16px;
}

.republish-article__info textarea:focus {
border-color: #a7a7a7;
box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.2);
border-color: #1e1e1e;
}

.republish-article__copy-button {
margin-top: 20px;
}

.republish-format-tabs {
display: flex;
margin-bottom: 1.5rem;
border-bottom: 1px solid #e0e0e0;
}

.republish-format-tabs__button {
background: none;
border: none;
padding: 0.75rem 1.5rem;
cursor: pointer;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-weight: 600;
color: #6c6c6c;
position: relative;
transition: color 0.2s ease;
border-bottom: 3px solid transparent;
border-radius: 0;
margin-bottom: -1px;
font-size: 20px;

&:hover {
color: #e0e0e0;
}

&:focus {
outline: none;
color: #e0e0e0;
}
}

.republish-format-tabs__button--active {
color: #1e1e1e;
border-bottom-color: currentcolor;
}

.republish-content {
display: none;
}

.republish-content--active {
display: block;
}

.republish-content-container {
position: relative;
}

.republish-content__input,
.republish-content__textarea {
margin: 1em 0 1em 0;
width: 100%;
}

.republish-content__input {
display: block;
}

.plain-text-field {
margin-bottom: 1.5rem;
}

.plain-text-field__label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #1e1e1e;
font-size: 16px;
}

.plain-text-field__input {
width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
background-color: white;
margin-bottom: 0.5rem;

&:focus {
outline: none;
border-color: #1e1e1e;
}
}

.plain-text-field__button {
background: #2a7ac2;
border: 1px solid #255b98;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.25em;
cursor: pointer;
font-size: 16px;
text-shadow: 1px 1px 1px #255b98;

&:hover {
background: #2863a7;
text-decoration: none;
}
}

.republish-article__copy-button--main {
display: none;
}

.republish-article__copy-button--main.show-for-html {
display: inline-block;
}
94 changes: 56 additions & 38 deletions assets/republish-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,74 @@
*/
document.addEventListener("DOMContentLoaded", () => {
/**
* Selects the text in the textarea when it is focused.
* Handle tab switching for format selection.
*/
document
.querySelector(".republish-article .republish-article__info textarea")
?.addEventListener("focus", (event) => {
const tabButtons = document.querySelectorAll(".republish-format-tabs__button");
const tabContents = document.querySelectorAll(".republish-content");

tabButtons.forEach((button) => {
button.addEventListener("click", (event) => {
event.preventDefault();
const targetTab = button.getAttribute("data-tab");

tabButtons.forEach((btn) => btn.classList.remove("republish-format-tabs__button--active"));
tabContents.forEach((content) => content.classList.remove("republish-content--active"));

button.classList.add("republish-format-tabs__button--active");
const targetContent = document.querySelector(`[data-tab-content="${targetTab}"]`);
if (targetContent) {
targetContent.classList.add("republish-content--active");
}

// Show/hide main copy button based on active tab
const mainCopyButton = document.querySelector(".republish-article__copy-button--main");
if (mainCopyButton) {
if (targetTab === "html") {
mainCopyButton.classList.add("show-for-html");
} else {
mainCopyButton.classList.remove("show-for-html");
}
}
});
});

/**
* Selects the text in the active textarea when it is focused.
*/
const textareas = document.querySelectorAll(".republish-content__textarea");
textareas.forEach((textarea) => {
textarea.addEventListener("focus", (event) => {
event.target.select();
});
});

/**
* Copies the text in the textarea to the clipboard when the copy button is clicked.
* Copies the text in the active textarea to the clipboard when the copy button is clicked.
*/
document
.querySelector(".republish-article .republish-article__copy-button")
.querySelector(".republish-article__copy-button")
?.addEventListener("click", (event) => {
event.preventDefault();
const textarea = document.querySelector(
".republish-article .republish-article__info textarea"
);
const success = copyTextToClipboard(textarea.value);

if (success) {
event.target.innerText = __("Copied!", "the-city-features");

setTimeout(() => {
event.target.innerText = __(
"Copy to clipboard",
"the-city-features"
);
}, 2000);

const activeTextarea = document.querySelector(".republish-content.republish-content--active.republish-content__textarea");

if (!activeTextarea) {
return;
}

ClipboardUtils.copyFromElement(activeTextarea, event.target);
});

/**
* Copies the given text to the clipboard.
*
* @param {string} text The text to copy to the clipboard.
*
* @return {boolean} True if the text was copied to the clipboard, false otherwise.
* Handle individual field copy buttons for plain text format.
*/
const copyTextToClipboard = (text) => {
// Check if the clipboard API is available.
if (!navigator.clipboard) {
return false;
}
// Copy the text to the clipboard.
navigator.clipboard
.writeText(text)
.then(() => true)
.catch(() => false);

return true;
};
const copyFieldButtons = document.querySelectorAll(".plain-text-field__button");
copyFieldButtons.forEach((button) => {
button.addEventListener("click", (event) => {
event.preventDefault();

const targetSelector = button.getAttribute("data-target");
ClipboardUtils.copyFromElement(targetSelector, button);
});
});
});
Loading