Initial changes to make it work with gitea

This commit is contained in:
Sebastian Sauer 2023-07-18 21:37:35 +02:00
parent 8dc05773e2
commit dd94e01fda
6 changed files with 34 additions and 258 deletions

@ -1,6 +1,12 @@
# Conventional Comments button
# Conventional Comments button for gitea
This is a tiny extension that adds a conventional comment button to GitLab file explorer comments, allowing to quickly leave a structured semantic comment during your MR reviews!
This is a tiny extension that adds a conventional comment button to gitea pull request comments, allowing to quickly leave a structured semantic comment during your PR reviews!
## Attribution
This project was forked from https://gitlab.com/conventionalcomments/conventional-comments-button to make it usable with gitea.
Please see LICENSE file for the original license.
## Demo

@ -1,5 +1,5 @@
{
"name": "Conventional comments button",
"name": "gitea conventional comments buttons",
"version": "0.0.1",
"manifest_version": 3,
"description": "An extension to quickly add conventional comments",

@ -1,5 +1,5 @@
const scriptId = "conventional-comments-button";
const defaultHost = 'https://gitlab.com';
const defaultHost = 'https://gitea.com';
async function registerContentScripts(hosts) {
hosts = hosts.split('\n');
@ -7,7 +7,6 @@ async function registerContentScripts(hosts) {
for (var index in hosts) {
hosts[index] = hosts[index].trim() + "/*";
}
await chrome.scripting.registerContentScripts([{
id: scriptId,
matches: hosts,

@ -1,233 +0,0 @@
(function () {
'use strict';
function NestedProxy(target) {
return new Proxy(target, {
get(target, prop) {
if (typeof target[prop] !== 'function') {
return new NestedProxy(target[prop]);
}
return (...arguments_) =>
new Promise((resolve, reject) => {
target[prop](...arguments_, result => {
if (chrome.runtime.lastError) {
reject(new Error(chrome.runtime.lastError.message));
} else {
resolve(result);
}
});
});
}
});
}
const chromeP = globalThis.chrome && new NestedProxy(globalThis.chrome);
const gotScripting = typeof chrome === 'object' && 'scripting' in chrome;
function castTarget(target) {
return typeof target === 'object' ? target : {
tabId: target,
frameId: 0,
};
}
async function executeFunction(target, function_, ...args) {
const { frameId, tabId } = castTarget(target);
if (gotScripting) {
const [injection] = await chrome.scripting.executeScript({
target: {
tabId,
frameIds: [frameId],
},
func: function_,
args,
});
return injection === null || injection === void 0 ? void 0 : injection.result;
}
const [result] = await chromeP.tabs.executeScript(tabId, {
code: `(${function_.toString()})(...${JSON.stringify(args)})`,
frameId,
});
return result;
}
function arrayOrUndefined(value) {
return typeof value === 'undefined' ? undefined : [value];
}
function insertCSS({ tabId, frameId, files, allFrames, matchAboutBlank, runAt, }) {
for (let content of files) {
if (typeof content === 'string') {
content = { file: content };
}
if (gotScripting) {
void chrome.scripting.insertCSS({
target: {
tabId,
frameIds: arrayOrUndefined(frameId),
allFrames,
},
files: 'file' in content ? [content.file] : undefined,
css: 'code' in content ? content.code : undefined,
});
}
else {
void chromeP.tabs.insertCSS(tabId, {
...content,
matchAboutBlank,
allFrames,
frameId,
runAt: runAt !== null && runAt !== void 0 ? runAt : 'document_start',
});
}
}
}
async function executeScript({ tabId, frameId, files, allFrames, matchAboutBlank, runAt, }) {
let lastInjection;
for (let content of files) {
if (typeof content === 'string') {
content = { file: content };
}
if (gotScripting) {
if ('code' in content) {
throw new Error('chrome.scripting does not support injecting strings of `code`');
}
void chrome.scripting.executeScript({
target: {
tabId,
frameIds: arrayOrUndefined(frameId),
allFrames,
},
files: [content.file],
});
}
else {
if ('code' in content) {
await lastInjection;
}
lastInjection = chromeP.tabs.executeScript(tabId, {
...content,
matchAboutBlank,
allFrames,
frameId,
runAt,
});
}
}
}
const patternValidationRegex = /^(https?|wss?|file|ftp|\*):\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^file:\/\/\/.*$|^resource:\/\/(\*|\*\.[^*/]+|[^*/]+)\/.*$|^about:/;
const isFirefox = typeof navigator === 'object' && navigator.userAgent.includes('Firefox/');
const allStarsRegex = isFirefox ? /^(https?|wss?):[/][/][^/]+([/].*)?$/ : /^https?:[/][/][^/]+([/].*)?$/;
const allUrlsRegex = /^(https?|file|ftp):[/]+/;
function getRawRegex(matchPattern) {
if (!patternValidationRegex.test(matchPattern)) {
throw new Error(matchPattern + ' is an invalid pattern, it must match ' + String(patternValidationRegex));
}
let [, protocol, host, pathname] = matchPattern.split(/(^[^:]+:[/][/])([^/]+)?/);
protocol = protocol
.replace('*', isFirefox ? '(https?|wss?)' : 'https?')
.replace(/[/]/g, '[/]');
host = (host !== null && host !== void 0 ? host : '')
.replace(/^[*][.]/, '([^/]+.)*')
.replace(/^[*]$/, '[^/]+')
.replace(/[.]/g, '[.]')
.replace(/[*]$/g, '[^.]+');
pathname = pathname
.replace(/[/]/g, '[/]')
.replace(/[.]/g, '[.]')
.replace(/[*]/g, '.*');
return '^' + protocol + host + '(' + pathname + ')?$';
}
function patternToRegex(...matchPatterns) {
if (matchPatterns.length === 0) {
return /$./;
}
if (matchPatterns.includes('<all_urls>')) {
return allUrlsRegex;
}
if (matchPatterns.includes('*://*/*')) {
return allStarsRegex;
}
return new RegExp(matchPatterns.map(x => getRawRegex(x)).join('|'));
}
const gotNavigation = typeof chrome === 'object' && 'webNavigation' in chrome;
async function isOriginPermitted(url) {
return chromeP.permissions.contains({
origins: [new URL(url).origin + '/*'],
});
}
async function wasPreviouslyLoaded(target, assets) {
const loadCheck = (key) => {
const wasLoaded = document[key];
document[key] = true;
return wasLoaded;
};
return executeFunction(target, loadCheck, JSON.stringify(assets));
}
async function registerContentScript(contentScriptOptions, callback) {
const { js = [], css = [], matchAboutBlank, matches, excludeMatches, runAt, } = contentScriptOptions;
let { allFrames } = contentScriptOptions;
if (gotNavigation) {
allFrames = false;
}
else if (allFrames) {
console.warn('`allFrames: true` requires the `webNavigation` permission to work correctly: https://github.com/fregante/content-scripts-register-polyfill#permissions');
}
const matchesRegex = patternToRegex(...matches);
const excludeMatchesRegex = patternToRegex(...excludeMatches !== null && excludeMatches !== void 0 ? excludeMatches : []);
const inject = async (url, tabId, frameId = 0) => {
if (!matchesRegex.test(url)
|| excludeMatchesRegex.test(url)
|| !await isOriginPermitted(url)
|| await wasPreviouslyLoaded({ tabId, frameId }, { js, css })
) {
return;
}
insertCSS({
tabId,
frameId,
files: css,
matchAboutBlank,
runAt,
});
await executeScript({
tabId,
frameId,
files: js,
matchAboutBlank,
runAt,
});
};
const tabListener = async (tabId, { status }, { url }) => {
if (status && url) {
void inject(url, tabId);
}
};
const navListener = async ({ tabId, frameId, url, }) => {
void inject(url, tabId, frameId);
};
if (gotNavigation) {
chrome.webNavigation.onCommitted.addListener(navListener);
}
else {
chrome.tabs.onUpdated.addListener(tabListener);
}
const registeredContentScript = {
async unregister() {
if (gotNavigation) {
chrome.webNavigation.onCommitted.removeListener(navListener);
}
else {
chrome.tabs.onUpdated.removeListener(tabListener);
}
},
};
if (typeof callback === 'function') {
callback(registeredContentScript);
}
return registeredContentScript;
}
if (typeof chrome === 'object' && !chrome.contentScripts) {
chrome.contentScripts = { register: registerContentScript };
}
}());

@ -16,7 +16,8 @@
#conventionalCommentButtonContainer {
display: flex;
align-items: flex-end;
margin-left:0;
margin: 5px 0;
}
#conventionalCommentButtonContainer .buttonContainer {

@ -5,7 +5,7 @@ const bugIcon = `<svg aria-hidden="true" focusable="false" data-prefix="fas" dat
const questionIcon = `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="question" class="svg-inline--fa fa-question fa-w-12" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M202.021 0C122.202 0 70.503 32.703 29.914 91.026c-7.363 10.58-5.093 25.086 5.178 32.874l43.138 32.709c10.373 7.865 25.132 6.026 33.253-4.148 25.049-31.381 43.63-49.449 82.757-49.449 30.764 0 68.816 19.799 68.816 49.631 0 22.552-18.617 34.134-48.993 51.164-35.423 19.86-82.299 44.576-82.299 106.405V320c0 13.255 10.745 24 24 24h72.471c13.255 0 24-10.745 24-24v-5.773c0-42.86 125.268-44.645 125.268-160.627C377.504 66.256 286.902 0 202.021 0zM192 373.459c-38.196 0-69.271 31.075-69.271 69.271 0 38.195 31.075 69.27 69.271 69.27s69.271-31.075 69.271-69.271-31.075-69.27-69.271-69.27z"></path></svg>`;
const commentIcon = `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="comment" class="svg-inline--fa fa-comment fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 32C114.6 32 0 125.1 0 240c0 49.6 21.4 95 57 130.7C44.5 421.1 2.7 466 2.2 466.5c-2.2 2.3-2.8 5.7-1.5 8.7S4.8 480 8 480c66.3 0 116-31.8 140.6-51.4 32.7 12.3 69 19.4 107.4 19.4 141.4 0 256-93.1 256-208S397.4 32 256 32z"></path></svg>`;
const homeIcon = `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="home" class="svg-inline--fa fa-home fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z"></path></svg>`;
const stickyNoteIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H288V352c0-17.7 14.3-32 32-32h80V96c0-8.8-7.2-16-16-16H64zM288 480H64c-35.3 0-64-28.7-64-64V96C0 60.7 28.7 32 64 32H384c35.3 0 64 28.7 64 64V320v5.5c0 17-6.7 33.3-18.7 45.3l-90.5 90.5c-12 12-28.3 18.7-45.3 18.7H288z"/></svg>`;
const stickyNoteIcon = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path path fill="currentColor" d="M64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H288V352c0-17.7 14.3-32 32-32h80V96c0-8.8-7.2-16-16-16H64zM288 480H64c-35.3 0-64-28.7-64-64V96C0 60.7 28.7 32 64 32H384c35.3 0 64 28.7 64 64V320v5.5c0 17-6.7 33.3-18.7 45.3l-90.5 90.5c-12 12-28.3 18.7-45.3 18.7H288z"/></svg>`;
const semanticLabels = {
praise: {
@ -89,14 +89,13 @@ const semanticButtonClickHandler = (e, { textarea, label, blocking }) => {
const buttonGenerator = (textarea, parent, label, blocking) => {
const button = document.createElement("button");
button.classList.add("has-tooltip");
button.setAttribute("data-title", semanticLabels[label].text);
button.setAttribute("data-tooltip-content", semanticLabels[label].text);
button.innerHTML = semanticLabels[label].icon;
if (blocking) {
button.classList.add("blocking");
button.setAttribute(
"data-title",
"data-tooltip-content",
`${semanticLabels[label].text} (blocking)`
);
}
@ -119,14 +118,13 @@ const buttonPairGenerator = (textarea, parent, label) => {
};
const addSemanticButton = (element) => {
const parent = element
.closest(".div-dropzone-wrapper")
.querySelector(".comment-toolbar");
const parent = element.querySelector('.field.footer');
const container = document.createElement("div");
container.id = "conventionalCommentButtonContainer";
container.classList.add("ui");
container.classList.add("left");
Object.keys(semanticLabels).forEach((label) => {
buttonPairGenerator(element, container, label);
buttonPairGenerator(element.querySelector('textarea.markdown-text-editor'), container, label);
});
parent.classList.remove("clearfix");
parent.classList.add("has-conventional-comments-buttons");
@ -142,13 +140,18 @@ const saveChanges = (element) => {
element.dispatchEvent(event);
};
setInterval(function () {
document
.querySelectorAll(
"#note_note:not([data-semantic-button-initialized]), #note-body:not([data-semantic-button-initialized]), #review-note-body:not([data-semantic-button-initialized])"
)
.forEach(function (note) {
note.dataset.semanticButtonInitialized = "true";
addSemanticButton(note);
});
}, 1000);
// Only add the interval if we're on the files diff page
// (as on all other pages there is no need for conventional comments)
if(document.querySelector('.page-content.repository.view.issue.pull.files.diff')) {
setInterval(function () {
document
.querySelectorAll(
".field.comment-code-cloud:not([data-semantic-button-initialized])"
)
.forEach(function (note) {
note.dataset.semanticButtonInitialized = "true";
note.querySelector('.markup-info').remove();
addSemanticButton(note);
});
}, 1000);
}