Initial changes to make it work with gitea
This commit is contained in:
parent
8dc05773e2
commit
dd94e01fda
10
README.md
10
README.md
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user