feat: add google-meet-transcripts-extension and caption-extension
@@ -0,0 +1 @@
|
||||
.qodo
|
||||
@@ -0,0 +1,19 @@
|
||||
"use strict"
|
||||
import logger from './logger.js';
|
||||
|
||||
logger.debug("Hello, world from background!")
|
||||
|
||||
function setBadgeText(enabled) {
|
||||
const text = enabled ? "ON" : "OFF"
|
||||
void chrome.action.setBadgeText({text: text})
|
||||
}
|
||||
|
||||
function startUp() {
|
||||
chrome.storage.sync.get("slider", (data) => {
|
||||
setBadgeText(!!data.slider)
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure the background script always runs.
|
||||
chrome.runtime.onStartup.addListener(startUp)
|
||||
chrome.runtime.onInstalled.addListener(startUp)
|
||||
@@ -0,0 +1,5 @@
|
||||
var config = {
|
||||
loggerLevel: 'debug', //change it to 'debug' for all logs, 'info' or more to reduce the log output in the production
|
||||
defaultCaptureInterval: 100, //change it to 1000 for 1 second interval
|
||||
};
|
||||
export default config;
|
||||
@@ -0,0 +1,64 @@
|
||||
"use strict"
|
||||
|
||||
const blurFilter = "blur(6px)"
|
||||
let textToBlur = ""
|
||||
|
||||
// Search this DOM node for text to blur and blur the parent element if found.
|
||||
function processNode(node) {
|
||||
if (node.childNodes.length > 0) {
|
||||
Array.from(node.childNodes).forEach(processNode)
|
||||
}
|
||||
if (node.nodeType === Node.TEXT_NODE &&
|
||||
node.textContent !== null && node.textContent.trim().length > 0) {
|
||||
const parent = node.parentElement
|
||||
if (parent !== null &&
|
||||
(parent.tagName === 'SCRIPT' || parent.style.filter === blurFilter)) {
|
||||
// Already blurred
|
||||
return
|
||||
}
|
||||
if (node.textContent.includes(textToBlur)) {
|
||||
blurElement(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function blurElement(elem) {
|
||||
elem.style.filter = blurFilter
|
||||
console.debug("blurred id:" + elem.id + " class:" + elem.className +
|
||||
" tag:" + elem.tagName + " text:" + elem.textContent)
|
||||
}
|
||||
|
||||
// Create a MutationObserver to watch for changes to the DOM.
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.addedNodes.length > 0) {
|
||||
mutation.addedNodes.forEach(processNode)
|
||||
} else {
|
||||
processNode(mutation.target)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Enable the content script by default.
|
||||
let enabled = true
|
||||
const keys = ["slider", "item"]
|
||||
|
||||
chrome.storage.sync.get(keys, (data) => {
|
||||
if (data.enabled === false) {
|
||||
enabled = false
|
||||
}
|
||||
if (data.item) {
|
||||
textToBlur = data.item
|
||||
}
|
||||
// Only start observing the DOM if the extension is enabled and there is text to blur.
|
||||
if (enabled && textToBlur.trim().length > 0) {
|
||||
observer.observe(document, {
|
||||
attributes: false,
|
||||
characterData: true,
|
||||
childList: true,
|
||||
subtree: true,
|
||||
})
|
||||
// Loop through all elements on the page for initial processing.
|
||||
processNode(document)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,280 @@
|
||||
"use strict";
|
||||
|
||||
(async () => {
|
||||
// This file cannot import config.js or logger.js directly because it runs in a different context (source:https://stackoverflow.com/a/53033388/4235602)
|
||||
// So we need to load them dynamically. But our config.js and logger.js are in the same directory and can import each other
|
||||
try {
|
||||
//#region load logger
|
||||
const loggerSrc = chrome.runtime.getURL("logger.js");
|
||||
const loggerModule = await import(loggerSrc);
|
||||
const logger = loggerModule.default;
|
||||
|
||||
logger.debug("Logger initialized successfully");
|
||||
logger.debug("Content script starting...");
|
||||
//#endregion
|
||||
|
||||
//#region load config
|
||||
const configSrc = chrome.runtime.getURL("config.js");
|
||||
const configModule = await import(configSrc);
|
||||
const config = configModule.default;
|
||||
|
||||
logger.debug("Config loaded:", config);
|
||||
//#endregion
|
||||
|
||||
class CaptionCapture {
|
||||
constructor() {
|
||||
this.isCapturing = false;
|
||||
this.intervalId = null;
|
||||
this.lastSpans = [];
|
||||
this.captionsLog = [];
|
||||
this.mergedCaption = [];
|
||||
this.subtitleText = null;
|
||||
this.checkSubtitleInterval = config.defaultCaptureInterval;
|
||||
}
|
||||
|
||||
// Function to detect and get the caption spans as an array
|
||||
detectCaptionAsArray() {
|
||||
try {
|
||||
let spans = document.querySelectorAll('div.ygicle.VbkSUe');
|
||||
if (!spans || spans.length === 0) {
|
||||
logger.debug("No caption spans found");
|
||||
return [];
|
||||
}
|
||||
return Array.from(spans).map(span => span.innerText.trim()) || [];
|
||||
} catch (error) {
|
||||
logger.error("Error detecting captions:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get absolute time in HH:MM:SS,mmm format (Real-Time)
|
||||
getCurrentTimestamp() {
|
||||
let now = new Date();
|
||||
let hours = String(now.getHours()).padStart(2, '0');
|
||||
let minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
let seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
let millis = String(now.getMilliseconds()).padStart(3, '0');
|
||||
|
||||
return `${hours}:${minutes}:${seconds},${millis}`; // Absolute time format
|
||||
}
|
||||
|
||||
// Function to save new words with timestamp
|
||||
saveCaptionByWords(timestamp, newText) {
|
||||
this.captionsLog.push({ time: timestamp, text: newText });
|
||||
logger.debug(`${timestamp} --> ${newText}`); // Print in subtitle format
|
||||
}
|
||||
|
||||
formatTimestamp(ms) {
|
||||
const h = String(Math.floor(ms / 3600000)).padStart(2, "0");
|
||||
ms %= 3600000;
|
||||
const m = String(Math.floor(ms / 60000)).padStart(2, "0");
|
||||
ms %= 60000;
|
||||
const s = String(Math.floor(ms / 1000)).padStart(2, "0");
|
||||
const msStr = String(ms % 1000).padStart(3, "0");
|
||||
return `${h}:${m}:${s},${msStr}`;
|
||||
}
|
||||
|
||||
mergeCaptions(captions, gap = 1000) {
|
||||
if (!captions || captions.length === 0) return [];
|
||||
|
||||
const parseTime = (timestamp) => {
|
||||
const [h, m, sMs] = timestamp.split(":");
|
||||
const [s, ms] = sMs.split(",");
|
||||
return (
|
||||
parseInt(h) * 3600000 +
|
||||
parseInt(m) * 60000 +
|
||||
parseInt(s) * 1000 +
|
||||
parseInt(ms)
|
||||
);
|
||||
};
|
||||
|
||||
const merged = [];
|
||||
let current = {
|
||||
start: parseTime(captions[0].time),
|
||||
end: parseTime(captions[0].time),
|
||||
texts: [captions[0].text]
|
||||
};
|
||||
|
||||
for (let i = 1; i < captions.length; i++) {
|
||||
const entry = captions[i];
|
||||
const time = parseTime(entry.time);
|
||||
const timeDiff = time - current.end;
|
||||
|
||||
if (timeDiff > gap) {
|
||||
merged.push({
|
||||
start: this.formatTimestamp(current.start),
|
||||
end: this.formatTimestamp(current.end),
|
||||
text: current.texts.join(" ")
|
||||
});
|
||||
|
||||
current = {
|
||||
start: time,
|
||||
end: time,
|
||||
texts: [entry.text]
|
||||
};
|
||||
} else {
|
||||
current.end = time;
|
||||
current.texts.push(entry.text);
|
||||
}
|
||||
}
|
||||
|
||||
// Push the last group
|
||||
merged.push({
|
||||
start: this.formatTimestamp(current.start),
|
||||
end: this.formatTimestamp(current.end),
|
||||
text: current.texts.join(" ")
|
||||
});
|
||||
|
||||
this.mergedCaption = merged;
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
|
||||
printMergedSubtitle() {
|
||||
const merged = this.mergeCaptions(this.getCapturedCaptions(), 1000);
|
||||
if (merged.length === 0) {
|
||||
logger.debug("No subtitles to print.");
|
||||
return;
|
||||
}
|
||||
|
||||
const parseTime = (timestamp) => {
|
||||
const [h, m, sMs] = timestamp.split(":");
|
||||
const [s, ms] = sMs.split(",");
|
||||
return (
|
||||
parseInt(h) * 3600000 +
|
||||
parseInt(m) * 60000 +
|
||||
parseInt(s) * 1000 +
|
||||
parseInt(ms)
|
||||
);
|
||||
};
|
||||
|
||||
const baseTime = parseTime(merged[0].start); // time of first word
|
||||
|
||||
const toRelative = (absTime) => {
|
||||
const ms = parseTime(absTime) - baseTime;
|
||||
return this.formatTimestamp(ms);
|
||||
};
|
||||
|
||||
this.subtitleText = merged.map((item, index) =>
|
||||
`${index + 1}\n${toRelative(item.start)} --> ${toRelative(item.end)}\n${item.text}\n`
|
||||
).join("\n");
|
||||
|
||||
logger.debug(this.subtitleText);
|
||||
|
||||
return this.subtitleText;
|
||||
}
|
||||
|
||||
generateSrtFilename() {
|
||||
const now = new Date();
|
||||
const pad = (n) => String(n).padStart(2, '0');
|
||||
return `meet_caption_${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}_${pad(now.getHours())}${pad(now.getMinutes())}.srt`;
|
||||
}
|
||||
|
||||
downloadSrtFile() {
|
||||
if (!this.subtitleText) {
|
||||
logger.warn("No subtitle content found. Please call printMergedSubtitle() first.");
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = this.generateSrtFilename();
|
||||
const blob = new Blob([this.subtitleText], { type: "text/plain" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
a.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Function to start capturing captions
|
||||
startCapturing() {
|
||||
if (this.isCapturing) {
|
||||
logger.debug("Caption capturing is already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCapturing = true;
|
||||
logger.debug("Caption capturing started.");
|
||||
|
||||
this.intervalId = setInterval(() => {
|
||||
let currentSpans = this.detectCaptionAsArray();
|
||||
|
||||
let lastOldSpan = this.lastSpans[this.lastSpans.length - 1] || "";
|
||||
let lastNewSpan = currentSpans[currentSpans.length - 1] || "";
|
||||
|
||||
let isLastCaptionChanged = lastNewSpan !== lastOldSpan;
|
||||
|
||||
if (isLastCaptionChanged) {
|
||||
this.lastSpans = currentSpans; // Update stored spans
|
||||
let timestamp = this.getCurrentTimestamp();
|
||||
|
||||
// Get only the new part of the last span
|
||||
let newText = lastNewSpan.replace(lastOldSpan, "").trim();
|
||||
|
||||
if (newText) {
|
||||
logger.debug(newText)
|
||||
const wordCount = newText.trim().split(/\s+/).length;
|
||||
const BurstTextCount = 80;
|
||||
if (wordCount <= (this.checkSubtitleInterval / 100) * BurstTextCount) {
|
||||
this.saveCaptionByWords(timestamp, newText);
|
||||
} else {
|
||||
logger.debug(`Skipped burst text with ${wordCount} words at ${timestamp}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this.checkSubtitleInterval); // Runs every 100ms for better precision
|
||||
}
|
||||
|
||||
// Function to stop capturing captions
|
||||
stopCapturing() {
|
||||
if (!this.isCapturing) {
|
||||
logger.debug("Caption capturing is not running.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.isCapturing = false;
|
||||
clearInterval(this.intervalId);
|
||||
logger.debug("Caption capturing stopped.");
|
||||
}
|
||||
|
||||
// Function to get the saved captions log
|
||||
getCapturedCaptions() {
|
||||
return this.captionsLog;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize CaptionCapture and message listener
|
||||
const captionCapture = new CaptionCapture();
|
||||
logger.debug("CaptionCapture initialized");
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
logger.debug("start of listener in content.js");
|
||||
|
||||
if (message.action === "startCapture") {
|
||||
logger.debug("startCapture content.js");
|
||||
captionCapture.startCapturing();
|
||||
sendResponse({ status: "started" });
|
||||
} else if (message.action === "stopCapture") {
|
||||
logger.debug("stopCapture content.js");
|
||||
captionCapture.stopCapturing();
|
||||
sendResponse({ status: "stopped" });
|
||||
} else if (message.action === "downloadSrt") {
|
||||
alert("Downloading SRT file...");
|
||||
logger.debug("downloadSrt content.js");
|
||||
captionCapture.printMergedSubtitle();
|
||||
captionCapture.downloadSrtFile();
|
||||
sendResponse({ status: "downloadStarted" });
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug("Message listener set up");
|
||||
} catch (error) {
|
||||
logger.error("Error loading modules:", error);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import config from './config.js';
|
||||
|
||||
class Logger {
|
||||
static LEVELS = {
|
||||
debug: 0,
|
||||
info: 1,
|
||||
warn: 2,
|
||||
error: 3,
|
||||
none: 4
|
||||
};
|
||||
|
||||
static currentLevel = Logger.LEVELS.debug;
|
||||
|
||||
static setLevel(level) {
|
||||
if (level in Logger.LEVELS) {
|
||||
Logger.currentLevel = Logger.LEVELS[level];
|
||||
}
|
||||
}
|
||||
|
||||
static shouldLog(level, instanceLevel) {
|
||||
return Logger.LEVELS[level] >= instanceLevel;
|
||||
}
|
||||
|
||||
constructor(level = 'debug', scope = '') {
|
||||
this.level = Logger.LEVELS[level] ?? Logger.LEVELS.debug;
|
||||
this.scope = scope.toUpperCase();
|
||||
|
||||
for (const levelName of Object.keys(Logger.LEVELS)) {
|
||||
if (levelName === 'none') continue;
|
||||
|
||||
this[levelName] = (...args) => {
|
||||
if (Logger.shouldLog(levelName, this.level) && typeof console[levelName] === 'function') {
|
||||
console[levelName](
|
||||
`[${levelName.toUpperCase()}]${this.scope ? ` [${this.scope}]` : ''}`,
|
||||
...args
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a default global logger with no scope, using config value
|
||||
const logger = new Logger(config.loggerLevel);
|
||||
|
||||
export default logger;
|
||||
export { Logger };
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Meet caption saver",
|
||||
"version": "0.1.0",
|
||||
"description": "Save .srt file of conversation",
|
||||
"action": {
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"permissions": [
|
||||
"storage"
|
||||
],
|
||||
"host_permissions": [
|
||||
"http://meet.google.com/*",
|
||||
"https://meet.google.com/*"
|
||||
],
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"http://meet.google.com/*",
|
||||
"https://meet.google.com/*"
|
||||
],
|
||||
"js": [
|
||||
"content.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"background": {
|
||||
"service_worker": "background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"logger.js",
|
||||
"config.js"
|
||||
],
|
||||
"matches": [
|
||||
"http://meet.google.com/*",
|
||||
"https://meet.google.com/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
body {
|
||||
min-width: 100px;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.downloadBtn {
|
||||
margin: 6px 1px;
|
||||
padding: 3px 3px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff4444;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
padding: 0 10px;
|
||||
max-width: 200px;
|
||||
word-wrap: break-word;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* The switch - the box around the slider */
|
||||
.switch {
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 6px 1px;
|
||||
}
|
||||
|
||||
/* Hide default HTML checkbox */
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* The slider */
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.slider::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
input:checked+.slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked+.slider:before {
|
||||
transform: translateX(26px);
|
||||
/* Move the slider to the right when checked */
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round::before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.secret {
|
||||
margin: 5px;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>My popup</title>
|
||||
<link rel="stylesheet" type="text/css" href="popup.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<label class="switch">
|
||||
<input id="slider" type="checkbox">
|
||||
<span class="slider round"></span>
|
||||
</label>
|
||||
<button class="downloadBtn">Download .srt</button>
|
||||
<div id="errorMessage" class="error-message"></div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="popup.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,118 @@
|
||||
"use strict";
|
||||
import logger from './logger.js';
|
||||
|
||||
// #region Constants and Helper Functions
|
||||
function setBadgeText(enabled) {
|
||||
const text = enabled ? "ON" : "OFF"
|
||||
void chrome.action.setBadgeText({text: text})
|
||||
}
|
||||
|
||||
function isGoogleMeetPage(url) {
|
||||
return url && (url.startsWith('http://meet.google.com/') || url.startsWith('https://meet.google.com/'));
|
||||
}
|
||||
|
||||
function showErrorMessage(message) {
|
||||
const errorElement = document.getElementById("errorMessage");
|
||||
errorElement.textContent = message;
|
||||
errorElement.style.display = message ? "block" : "none";
|
||||
}
|
||||
|
||||
function handleContentScriptError(error) {
|
||||
logger.debug("Error sending message:", chrome.runtime.lastError);
|
||||
if (error.message.includes("Receiving end does not exist")) {
|
||||
showErrorMessage("Please refresh the Google Meet page to use this extension");
|
||||
} else {
|
||||
showErrorMessage("An error occurred. Please try again.");
|
||||
}
|
||||
}
|
||||
|
||||
function resetSliderState() {
|
||||
checkbox.checked = false;
|
||||
setBadgeText(false);
|
||||
chrome.storage.sync.set({ "slider": false });
|
||||
}
|
||||
// #endregion Constants and Helper Functions
|
||||
|
||||
// #region DOM Elements
|
||||
const checkbox = document.getElementById("slider");
|
||||
const downloadButton = document.querySelector(".downloadBtn");
|
||||
// #endregion DOM Elements
|
||||
|
||||
// #region Storage Operations
|
||||
function initializeSliderState() {
|
||||
chrome.storage.sync.get("slider", (data) => {
|
||||
const isEnabled = !!data.slider;
|
||||
checkbox.checked = isEnabled;
|
||||
setBadgeText(isEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
function saveSliderState(isChecked) {
|
||||
chrome.storage.sync.set({ "slider": isChecked }, (error) => {
|
||||
if (error) {
|
||||
showErrorMessage("Failed to save settings. Please try again.");
|
||||
return;
|
||||
}
|
||||
setBadgeText(isChecked);
|
||||
});
|
||||
}
|
||||
// #endregion Storage Operations
|
||||
|
||||
// #region Message Handling
|
||||
function sendMessageToContentScript(tabId, action) {
|
||||
chrome.tabs.sendMessage(tabId, { action }, (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
handleContentScriptError(chrome.runtime.lastError);
|
||||
return;
|
||||
}
|
||||
showErrorMessage(""); // Clear any previous error messages
|
||||
});
|
||||
}
|
||||
|
||||
function handleTabAction(action) {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
|
||||
if (tabs.length === 0) {
|
||||
showErrorMessage("No active tab found");
|
||||
resetSliderState();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTab = tabs[0];
|
||||
if (!isGoogleMeetPage(currentTab.url)) {
|
||||
showErrorMessage("Extension can only be used on Google Meet pages");
|
||||
resetSliderState();
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessageToContentScript(currentTab.id, action);
|
||||
});
|
||||
}
|
||||
// #endregion Message Handling
|
||||
|
||||
// #region Event Listeners
|
||||
function setupSliderListener() {
|
||||
checkbox.addEventListener("change", (event) => {
|
||||
const isChecked = event.target.checked;
|
||||
saveSliderState(isChecked);
|
||||
handleTabAction(isChecked ? "startCapture" : "stopCapture");
|
||||
});
|
||||
}
|
||||
|
||||
function setupDownloadButtonListener() {
|
||||
downloadButton.addEventListener("click", () => {
|
||||
handleTabAction("downloadSrt");
|
||||
});
|
||||
}
|
||||
// #endregion Event Listeners
|
||||
|
||||
// #region Initialization
|
||||
function initializePopup() {
|
||||
logger.debug("Initializing popup");
|
||||
initializeSliderState();
|
||||
setupSliderListener();
|
||||
setupDownloadButtonListener();
|
||||
}
|
||||
|
||||
// Start the popup initialization
|
||||
initializePopup();
|
||||
// #endregion Initialization
|
||||
@@ -0,0 +1 @@
|
||||
//!function(e,t,c,r,n,s){e.GoogleAnalyticsObject=r,e[r]=e[r]||function(){(e[r].q=e[r].q||[]).push(arguments)},e[r].l=1*new Date,n=t.createElement(c),s=t.getElementsByTagName(c)[0],n.async=1,n.src=chrome.runtime.getURL("analytics/analyticsSource.js"),n.id="laxis-track",s.parentNode.insertBefore(n,s)}(window,document,"script","ga");let firstScript=document.getElementsByTagName("script")[1],s=document.createElement("script");s.src=chrome.runtime.getURL("analytics/load.js"),firstScript.parentNode.insertBefore(s,firstScript);
|
||||
@@ -0,0 +1 @@
|
||||
let currentRight="1px",currentTop="100px",startTime=new Date,sessionList=[],bookmarkList=[{color:"crimson",enable:!1,name:"Important",code:"#E94B4B",attributes:{width:"15",height:"18",viewBox:"0 0 15 18",fill:"none"},content:[{type:"path",attributes:{d:"M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416",fill:"#E94B4B"}},{type:"path",attributes:{d:"M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416",stroke:"#E94B4B","stroke-linecap":"round","stroke-linejoin":"round"}}]},{color:"gold",enable:!1,name:"Follow up",code:"#FFD339",attributes:{width:"15",height:"18",viewBox:"0 0 15 18",fill:"none"},content:[{type:"path",attributes:{d:"M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416",fill:"#FFD339"}},{type:"path",attributes:{d:"M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416",stroke:"#FFD339","stroke-linecap":"round","stroke-linejoin":"round"}}]},{color:"yellowGreen",enable:!1,name:"Action",code:"#9CCC65",attributes:{width:"15",height:"18",viewBox:"0 0 15 18",fill:"none"},content:[{type:"path",attributes:{d:"M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416",fill:"#9CCC65"}},{type:"path",attributes:{d:"M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416",stroke:"#9CCC65","stroke-linecap":"round","stroke-linejoin":"round"}}]}];const dateStr=(startTime.getMonth()>8?startTime.getMonth()+1:"0"+(startTime.getMonth()+1))+"-"+(startTime.getDate()>9?startTime.getDate():"0"+startTime.getDate())+"-"+startTime.getFullYear();let autoSaveInterval,meetingStatus=0;
|
||||
@@ -0,0 +1 @@
|
||||
const CACHE=[],KEY_TRANSCRIPT_IDS="hangouts",CURRENT_INTERVAL="current_interval",ERROR_SAVING="error_saving",APPLICATION_SPEECH_IDS="speeches",SEARCH_TEXT_NO_MEETING_NAME="Meeting details";let SPEAKER_NAME_MAP,TRANSCRIPT_FORMAT_SPEAKER,TRANSCRIPT_FORMAT_SPEAKER_JOIN,TRANSCRIPT_FORMAT_SESSION_JOIN,TRANSCRIPT_FORMAT_MEETING,DEBUG;const XPATH_SELECTOR_PARTICIPANTS="//div[@aria-label='Show everyone']//*[@d='M15 8c0-1.42-.5-2.73-1.33-3.76.42-.14.86-.24 1.33-.24 2.21 0 4 1.79 4 4s-1.79 4-4 4c-.43 0-.84-.09-1.23-.21-.03-.01-.06-.02-.1-.03A5.98 5.98 0 0 0 15 8zm1.66 5.13C18.03 14.06 19 15.32 19 17v3h4v-3c0-2.18-3.58-3.47-6.34-3.87zM9 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2m0 9c-2.7 0-5.8 1.29-6 2.01V18h12v-1c-.2-.71-3.3-2-6-2M9 4c2.21 0 4 1.79 4 4s-1.79 4-4 4-4-1.79-4-4 1.79-4 4-4zm0 9c2.67 0 8 1.34 8 4v3H1v-3c0-2.66 5.33-4 8-4z']",XPATH_SELECTOR_PARTICIPANTS_V20210602="//button[@aria-label='Show everyone'] | //button[@aria-label='Mostrar a todos'] | //button[@aria-label='显示所有人'] |//button[@aria-label='顯示所有參與者'] |//button[@aria-label='顯示所有人'] | //button[@aria-label='Alle anzeigen'] | //button[@aria-label='Afficher tout le monde'] | //button[@aria-label='全員を表示'] | //button[@aria-label='Mostra tutti'] | //button[@aria-label='Mostrar todas as pessoas'] | //button[@aria-label='Mostrar todos']",XPATH_MEETING_DETAILS='//div[contains(@jscontroller,"rYZP8b")] | //div[@aria-label="Meeting details"]',XPATH_MEETING_DETAILS_V20210602="//button[@aria-label='Meeting details']",XPATH_SELECTOR_CHAT="//div[@aria-label='Chat with everyone']//*[@d='M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H4V4h16v12z']",XPATH_SELECTOR_CHAT_V20210602="//button[@aria-label='Chat with everyone'] | //button[@aria-label='Chatear con todos'] | //button[@aria-label='与所有人聊天'] | //button[@aria-label='與所有參與者進行即時通訊'] | //button[@aria-label='同所有人即時通訊'] | //button[@aria-label='Mit allen chatten'] | //button[@aria-label='Clavarder avec tout le monde'] | //button[@aria-label='全員とチャット'] | //button[@aria-label='Chatta con tutti'] | //button[@aria-label='Conversar com todos'] | //button[@aria-label='Discuter avec tous les participants'] | //button[@aria-label='Chat com todos']",turnOnText=["Turn on captions","Untertitel aktivieren","Activer les sous-titres","Activar subtítulos","Attiva sottotitoli","字幕をオンにする","开启字幕","開啟字幕"],turnOffText=["Turn off captions","Untertitel deaktivieren","Désactiver les sous-titres","Desactivar subtítulos","Disattiva sottotitoli","字幕をオフにする","关闭字幕","關閉字幕"],XPATH_TURN_ON_CAPTIONS_BUTTON=turnOnText.map((t=>`//div[text()='${t}']/ancestor::div[@role='button']`)).join(" | "),XPATH_TURN_ON_CAPTIONS_BUTTON_V20210602=turnOnText.map((t=>`//button[contains(@aria-label, '${t}')]`)).join(" | "),XPATH_TURN_OFF_CAPTIONS_BUTTON=turnOffText.map((t=>`//div[text()='${t}']/ancestor::div[@role='button']`)).join(" | "),XPATH_TURN_OFF_CAPTIONS_BUTTON_V20210602=turnOffText.map((t=>`//button[contains(@aria-label, '${t}')]`)).join(" | "),XPATH_CAPTION_OPEN_TOAST="//div[contains(@id, 'J9Hpafc')]",XPATH_CAPTION_OPEN_TOAST_V20210602="//div[contains(@id, 'J9Hpafc')]",XPATH_TITLE="//div[contains(@jscontroller,'WEGDee')]",XPATH_TITLE_V20220324="//div[contains(@jscontroller,'yEvoid')]",XPATH_TITLE_TOOLTIP="//div[contains(@id,'tooltip-c15')]";let captionsContainer=null,closedCaptionsAttachInterval=null,isTranscribing=!1,weTurnedCaptionsOn=!1,currentTranscriptId=null,currentSessionIndex=null,firstStart=!0,loadLocalStorage=0,startTimeStored=null,loginStatus=0,appUser="You";const SHORTCUT_KEY="DEBUG";
|
||||
@@ -0,0 +1 @@
|
||||
const domainUrl="https://app.laxis.tech",extensionId=chrome.runtime.id,loginUrl=domainUrl+"/login?source=chrome-extension&extensionId="+extensionId,upgradeUrl=domainUrl+"/settings/plan",signupUrl=domainUrl+"/signup?source=chrome-extension&extensionId="+extensionId,googleMeetUrl="https://meet.google.com/*";
|
||||
@@ -0,0 +1,251 @@
|
||||
const addTutorialPanel = () => {
|
||||
var e, t;
|
||||
e = chrome.runtime.getURL("feature/utilities/packages/html2pdf.bundle.min.js"), (t = document.createElement("script")).type = "application/javascript", t.src = e, document.head.appendChild(t);
|
||||
const n = document.getElementById("laxis-root");
|
||||
let l = document.getElementById("laxis-mainPanel");
|
||||
if (l) l.style.display = "block";
|
||||
else {
|
||||
l = document.createElement("div"), l.setAttribute("id", "laxis-mainPanel"), l.classList.add("panelBase"), l.style.width = "300px", l.style.backgroundColor = "#454953";
|
||||
const e = document.createElement("div");
|
||||
e.setAttribute("name", "laxis-rootHeader"), e.style.cursor = "move", e.style.width = "300px", e.style.height = "50px", e.style.backgroundColor = "#454953", e.style.borderRadius = "12px 12px 0 0", e.style.display = "flex", e.style.alignItems = "center", e.style.justifyContent = "space-between";
|
||||
const t = document.createElement("div");
|
||||
t.style.display = "flex", t.style.alignItems = "center", t.style.paddingLeft = "8px";
|
||||
const i = document.createElement("img");
|
||||
i.src = chrome.runtime.getURL("image/logo.png"), i.addEventListener("click", (function() {
|
||||
window.open(`${domainUrl}/login`)
|
||||
})), i.alt = "none", i.style.height = "30px", i.style.width = "90px", i.style.marginLeft = "-16px", i.style.cursor = "pointer", i.style.zIndex = "999", t.appendChild(i), e.appendChild(t);
|
||||
const o = document.createElement("div");
|
||||
o.style.display = "flex", o.style.alignItems = "center";
|
||||
const d = document.createElement("div");
|
||||
d.classList.add("imageContainer"), d.title = "Minimize", d.addEventListener("click", minimize);
|
||||
const a = createCollapseIcon();
|
||||
d.appendChild(a), o.appendChild(d), e.appendChild(o), l.appendChild(e);
|
||||
let s = document.createElement("div");
|
||||
s.style.padding = "24px", s.style.fontSize = "14px", s.style.color = "#ffffff", s.style.backgroundColor = "#292c35", s.style.borderRadius = "0 0 12px 12px";
|
||||
let c = document.createElement("div");
|
||||
c.innerHTML = "Highlight with colors", s.appendChild(c);
|
||||
let r = document.createElement("div");
|
||||
r.style.display = "flex", r.style.alignItems = "center", r.style.justifyContent = "center", bookmarkList.forEach((e => {
|
||||
let t = document.createElement("div");
|
||||
t.classList.add("tutorialContainer"), t.style.border = "1px solid #9e9e9e", t.style.backgroundColor = "#454953", t.style.display = "flex", t.style.alignItems = "center", t.style.justifyContent = "center";
|
||||
let n = createSVGIcon(e.attributes, e.content);
|
||||
t.appendChild(n), r.appendChild(t)
|
||||
})), s.appendChild(r);
|
||||
let p = document.createElement("div");
|
||||
p.style.textAlign = "center", p.innerHTML = "Use the three main color to hightlight any paragraph during the meeting.", s.appendChild(p);
|
||||
let m = document.createElement("div");
|
||||
m.innerHTML = "Select language", m.style.paddingTop = "32px", s.appendChild(m);
|
||||
let u = document.createElement("div");
|
||||
u.style.display = "flex", u.style.alignItems = "center", u.style.justifyContent = "center";
|
||||
const y = document.createElement("div");
|
||||
y.classList.add("tutorialContainer"), y.style.display = "flex", y.style.alignItems = "center", y.style.justifyContent = "center";
|
||||
const g = createCaptionOnIcon();
|
||||
y.appendChild(g), u.appendChild(y), s.appendChild(u);
|
||||
let h = document.createElement("div");
|
||||
h.style.textAlign = "center", h.innerHTML = "Laxis supports 69 languages in Google Meet", s.appendChild(h);
|
||||
let x = document.createElement("div");
|
||||
x.style.paddingTop = "32px", x.innerHTML = "Download", s.appendChild(x);
|
||||
let C = document.createElement("div");
|
||||
C.style.display = "flex", C.style.justifyContent = "center";
|
||||
let v = document.createElement("div");
|
||||
v.classList.add("tutorialContainer"), v.style.display = "flex", v.style.alignItems = "center", v.style.justifyContent = "center";
|
||||
let E = createDownloadIcon();
|
||||
v.appendChild(E), C.appendChild(v), s.appendChild(C);
|
||||
let b = document.createElement("div");
|
||||
b.style.textAlign = "center", b.innerHTML = "Laxis provides multiple formats for downloading the transcripts.", s.appendChild(b), l.appendChild(s), n.appendChild(l), dragElement(n)
|
||||
}
|
||||
};
|
||||
|
||||
function addRoot() {
|
||||
const e = document.createElement("div");
|
||||
e.setAttribute("id", "laxis-root"), e.style.position = "absolute", e.style.zIndex = "2000", e.style.right = currentRight, e.style.top = currentTop, e.style.height = "10px", e.style.width = "10px", document.body.appendChild(e)
|
||||
}
|
||||
|
||||
function addMiniPanel() {
|
||||
const e = document.getElementById("laxis-root"),
|
||||
t = document.createElement("div");
|
||||
t.setAttribute("id", "laxis-miniPanel"), t.classList.add("panelBase"), t.style.width = "60px", t.style.paddingBottom = "16px", t.style.backgroundColor = "#292c35";
|
||||
const n = document.createElement("div");
|
||||
n.setAttribute("name", "laxis-rootHeader"), n.style.cursor = "move", n.classList.add("col-12"), n.style.height = "50px", n.style.width = "50px", n.style.marginBottom = "-10px", n.style.cursor = "move", n.style.zIndex = "999";
|
||||
const l = document.createElement("img");
|
||||
l.src = chrome.runtime.getURL("image/logo.png"), l.addEventListener("click", (function() {
|
||||
window.open(`${domainUrl}/login`)
|
||||
})), l.style.cursor = "pointer", l.style.marginTop = "10px", l.style.height = "25px", l.style.width = "75px", l.style.marginLeft = "-16px", l.alt = "none", n.appendChild(l), t.appendChild(n);
|
||||
const i = document.createElement("div");
|
||||
i.title = "Expand", i.id = "laxis-expandPanel", i.classList.add("miniButtonContainer"), i.addEventListener("click", addTutorialPanel);
|
||||
const o = createExpandIcon();
|
||||
o.id = "expandInputIcon", i.appendChild(o), t.appendChild(i), e.appendChild(t), dragElement(e)
|
||||
}
|
||||
const addCaptionPanel = () => {
|
||||
const e = findButtonContainer(),
|
||||
t = document.getElementById("laxis-root");
|
||||
if (e && !e.__gmt_button_added) {
|
||||
e.__gmt_button_added = !0;
|
||||
const n = document.getElementById("laxis-miniPanel"),
|
||||
l = document.createElement("div");
|
||||
l.title = "Captions settings", l.setAttribute("id", "laxis-caption-toggle-mini"), l.classList.add("miniButtonContainer"), l.addEventListener("click", getToSettings);
|
||||
const i = createCaptionOnIcon();
|
||||
i.id = "captionIconMini", l.appendChild(i), n.appendChild(l);
|
||||
const o = document.createElement("div");
|
||||
o.title = "Download", o.setAttribute("id", "laxis-download-menu-mini"), o.classList.add("miniButtonContainer"), o.addEventListener("click", displayMenu);
|
||||
const d = createDownloadIcon();
|
||||
d.id = "downloadIconMini", d.style.width = "20px", d.style.height = "20px", o.appendChild(d), n.appendChild(o);
|
||||
const a = document.createElement("div");
|
||||
a.style.margin = "12px 5px 0px", a.style.padding = "6px 12px", a.style.borderRadius = "12px", a.style.backgroundColor = "#3e4149";
|
||||
for (let e = 0; e < bookmarkList.length; e++) {
|
||||
const t = document.createElement("div"),
|
||||
n = document.createElement("div");
|
||||
t.style.display = "flex", t.style.flexDirection = "column", t.style.alignItems = "center", n.title = `Highlight as ${bookmarkList[e].name}`, n.classList.add("flagContainerMini"), n.id = `laxis-highlight-${bookmarkList[e].code}-mini`, n.onclick = () => {
|
||||
highlight(e)
|
||||
};
|
||||
const l = createSVGIcon(bookmarkList[e].attributes, bookmarkList[e].content);
|
||||
n.appendChild(l), t.appendChild(n), a.appendChild(t)
|
||||
}
|
||||
n.appendChild(a), n.style.display = "block";
|
||||
let s = document.getElementById("laxis-mainPanel");
|
||||
s && t.removeChild(s);
|
||||
const c = document.createElement("div");
|
||||
c.setAttribute("id", "laxis-mainPanel"), c.classList.add("panelBase"), c.style.width = "300px", c.style.display = "none";
|
||||
const r = document.createElement("div");
|
||||
r.setAttribute("name", "laxis-rootHeader"), r.style.cursor = "move", r.style.width = "300px", r.style.height = "50px", r.style.backgroundColor = "#454953", r.style.borderRadius = "12px 12px 0 0", r.style.display = "flex", r.style.alignItems = "center", r.style.justifyContent = "space-between";
|
||||
const p = document.createElement("div");
|
||||
p.style.display = "flex", p.style.alignItems = "center", p.style.paddingLeft = "8px";
|
||||
const m = document.createElement("img");
|
||||
m.src = chrome.runtime.getURL("image/logo.png"), m.alt = "none", m.addEventListener("click", (function() {
|
||||
window.open(`${domainUrl}/login`)
|
||||
})), m.style.height = "30px", m.style.marginLeft = "-16px", m.style.cursor = "pointer", m.style.zIndex = "999", p.appendChild(m), r.appendChild(p);
|
||||
const u = document.createElement("div");
|
||||
u.style.display = "flex", u.style.alignItems = "center";
|
||||
const y = document.createElement("div");
|
||||
y.title = "Retrieve local data", y.setAttribute("id", "laxis-repair"), y.classList.add("imageContainer"), y.style.display = "none", y.addEventListener("click", extractLocalStorage);
|
||||
const g = document.createElement("img");
|
||||
g.id = "restoreIcon", g.src = chrome.runtime.getURL("image/repair.svg"), g.style.width = "15px", g.style.height = "18px", y.appendChild(g), u.appendChild(y);
|
||||
const h = document.createElement("div");
|
||||
h.title = "Captions settings", h.setAttribute("id", "laxis-caption-toggle"), h.classList.add("imageContainer"), h.addEventListener("click", getToSettings);
|
||||
const x = createCaptionOnIcon();
|
||||
x.id = "captionIcon", h.appendChild(x), u.appendChild(h);
|
||||
const C = document.createElement("div");
|
||||
C.title = "Auto-Scroll", C.setAttribute("id", "laxis-autoScroll");
|
||||
const v = document.createElement("input");
|
||||
v.type = "hidden", v.value = 1, v.setAttribute("id", "autoscroll"), v.onchange = () => autoScroll(), C.appendChild(v), C.classList.add("imageContainer"), C.addEventListener("click", (function() {
|
||||
let e = document.getElementById("autoscroll");
|
||||
"0" === e.value.toString() ? e.value = 1 : e.value = 0, autoScroll()
|
||||
}));
|
||||
const E = createAutoScrollIcon();
|
||||
E.style.width = "15px", E.style.height = "15px", C.appendChild(E), u.appendChild(C);
|
||||
const b = document.createElement("div");
|
||||
b.title = "Download", b.id = "laxis-openDownloadMenu", b.classList.add("imageContainer"), b.addEventListener("click", displayMenu);
|
||||
const f = createDownloadIcon();
|
||||
f.style.width = "15px", f.style.height = "15px", b.appendChild(f), u.appendChild(b);
|
||||
const L = document.createElement("div");
|
||||
L.title = "Minimize", L.id = "laxis-minimizePanel", L.classList.add("imageContainer"), L.addEventListener("click", minimize);
|
||||
const k = createCollapseIcon();
|
||||
k.style.width = "15px", k.style.height = "15px", L.appendChild(k), u.appendChild(L), r.appendChild(u), c.appendChild(r);
|
||||
const I = document.createElement("div");
|
||||
I.style.width = "300px", I.style.backgroundColor = "#292c35", I.style.paddingTop = "8px";
|
||||
const w = document.createElement("div");
|
||||
w.id = "login-prompt", w.style.display = "none", w.style.margin = "0px 8px", w.style.padding = "4px", w.style.textAlign = "center", w.addEventListener("click", signup), w.style.cursor = "pointer", w.innerHTML = "Please <span style='color:#2196f3'>log in</span> to enable autosave to Laxis Cloud.", w.style.borderRadius = "12px", w.style.border = "1px solid rgba(255, 255, 255, 0.5)", w.style.backgroundColor = "#454953", w.style.color = "#ffffff", w.style.fontSize = "10px";
|
||||
const T = document.createElement("div");
|
||||
T.id = "google-meet-quota", T.style.display = "none", T.style.margin = "0px 8px", T.style.padding = "4px", T.style.textAlign = "center", T.addEventListener("click", upgrade), T.style.cursor = "pointer", T.innerHTML = "Used Google Meet Quota: ", T.style.borderRadius = "12px", T.style.border = "1px solid rgba(255, 255, 255, 0.5)", T.style.backgroundColor = "#454953", T.style.color = "#ffffff", T.style.fontSize = "10px";
|
||||
const M = document.createElement("div");
|
||||
M.id = "meeting-name", M.style.fontWeight = "bold", M.style.padding = "8px", M.style.color = "#FFFFFF", I.appendChild(w), I.appendChild(T), I.appendChild(M);
|
||||
const B = document.createElement("div");
|
||||
B.style.paddingTop = "8px", B.style.width = "25%", I.style.paddingBottom = "8px", c.appendChild(I);
|
||||
const S = document.createElement("div");
|
||||
S.setAttribute("id", "feature"), S.style.width = "300px", S.style.backgroundColor = "#292c35", S.style.borderRadius = "0 0 12px 12px";
|
||||
const A = document.createElement("div");
|
||||
A.setAttribute("id", "caption"), A.style.height = "350px", A.style.width = "300px", A.style.overflowY = "auto", A.style.overflowX = "hidden", A.style.color = "#FFFFFF", A.style.paddingRight = "8px", A.onwheel = () => {
|
||||
v.value = 0
|
||||
}, S.appendChild(A);
|
||||
const H = document.createElement("div");
|
||||
H.id = "highlight-laxis", H.style.width = "300px", H.style.backgroundColor = "#292c35", H.style.display = "flex", H.style.justifyContent = "space-around", H.style.borderTop = "1px solid #e0e0e0", H.style.paddingTop = "10px", H.style.paddingBottom = "10px", H.classList.add("popup"), H.style.borderRadius = "0 0 12px 12px";
|
||||
for (let e = 0; e < bookmarkList.length; e++) {
|
||||
const t = document.createElement("div"),
|
||||
n = document.createElement("div");
|
||||
t.style.display = "flex", t.style.flexDirection = "column", t.style.alignItems = "center", n.style.textAlign = "center", n.style.fontSize = "10px", n.style.color = "#9e9e9e", n.style.marginTop = "4px", n.innerHTML = bookmarkList[e].name, n.id = `laxis-highlight-${bookmarkList[e].code}-title`;
|
||||
const l = document.createElement("div");
|
||||
l.title = `Highlight as ${bookmarkList[e].name}`, l.classList.add("flagContainer"), l.id = `laxis-highlight-${bookmarkList[e].code}`;
|
||||
const i = createSVGIcon(bookmarkList[e].attributes, bookmarkList[e].content);
|
||||
l.appendChild(i), l.onclick = () => {
|
||||
highlight(e)
|
||||
}, t.appendChild(l), t.appendChild(n), H.appendChild(t)
|
||||
}
|
||||
let N = document.createElement("div");
|
||||
N.id = "popup", N.className = "popupText", N.innerHTML = "Test", N.onclick = () => {
|
||||
clearTimeout(notificationsTimeout), N.classList.remove("show")
|
||||
}, H.appendChild(N), S.appendChild(H), c.appendChild(S), t.appendChild(c), dragElement(t), autoScroll(), document.getElementById("laxis-downloadMenu") || addDownloadMenu(), checkCaptionStatusInterval = setInterval(checkCaptionStatus, 500), turnOnCaptions(), debug("turned on caption"), removeCaptionPanel(), checkToken(), autoSaveInterval = setInterval((() => {
|
||||
let e = document.getElementById("autoSaveCheck");
|
||||
e && e.checked && Export2App(!0).catch((e => {
|
||||
console.error(e);
|
||||
const t = get(ERROR_SAVING) || [];
|
||||
set(ERROR_SAVING, [...t, e])
|
||||
}))
|
||||
}), 3e5), setTimeout(updateMeetingName, 500), clearInterval(checkOngoingMeeting)
|
||||
}
|
||||
},
|
||||
addDownloadMenu = () => {
|
||||
let e = document.createElement("div");
|
||||
e.id = "laxis-downloadMenu", e.className = "modal", e.style.display = "none";
|
||||
let t = document.createElement("div");
|
||||
t.className = "modal-content", t.id = "laxis-downloadMenuContent";
|
||||
let n = document.createElement("div");
|
||||
n.className = "modal-header", n.innerHTML = "Download", t.appendChild(n);
|
||||
let l = document.createElement("div"),
|
||||
i = document.createElement("div");
|
||||
i.className = "modal-body", i.innerHTML = "File Type<select name='extension' id='extension' value='txt'><option value='txt'>txt</option><option value='app'>Laxis Cloud</option><option value='doc'>doc</option><option value='pdf'>pdf</option></select>", l.appendChild(i);
|
||||
let o = document.createElement("div");
|
||||
o.className = "modal-body", o.innerHTML = "<div>Highlights</div><div><input type='checkbox' name='highlightCheck' id='highlightCheck' checked='true'/></div>", l.appendChild(o);
|
||||
let d = document.createElement("div");
|
||||
d.className = "modal-body", d.innerHTML = "<div>Timestamps</div><div><input type='checkbox' name='timestampCheck' id='timestampCheck'/></div>", l.appendChild(d);
|
||||
let a = document.createElement("div");
|
||||
a.className = "modal-body", a.title = "Autosave to Laxis every 5 minutes", a.style.cursor = "help", a.innerHTML = "<div>Autosave to Laxis Cloud</div><div><input type='checkbox' name='autoSaveCheck' id='autoSaveCheck'/></div>", l.appendChild(a), t.appendChild(l);
|
||||
let s = document.createElement("div");
|
||||
s.className = "modal-footer";
|
||||
let c = document.createElement("button");
|
||||
c.classList.add("modal-button"), c.id = "laxis-close-menu", c.style.border = "solid 2px #292c35", c.style.backgroundColor = "#454953", c.innerHTML = "Cancel", c.onclick = () => {
|
||||
document.getElementById("laxis-downloadMenu").style.display = "none"
|
||||
}, s.appendChild(c);
|
||||
let r = document.createElement("button");
|
||||
r.classList.add("modal-button"), r.style.backgroundColor = "#292c35", r.style.boxShadow = "box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.29)", r.innerHTML = "Download", r.addEventListener("click", downloadTranscript), r.setAttribute("id", "laxis-confirm-download"), s.appendChild(r), t.appendChild(s), e.appendChild(t);
|
||||
const p = document.getElementById("laxis-root");
|
||||
p && p.appendChild(e)
|
||||
},
|
||||
minimize = () => {
|
||||
const e = document.getElementById("laxis-mainPanel"),
|
||||
t = document.getElementById("laxis-miniPanel");
|
||||
e.style.display = "none", t.style.display = "block"
|
||||
},
|
||||
autoScroll = () => {
|
||||
let e = document.getElementById("laxis-autoScroll");
|
||||
e.style.border = "1px solid #2196f3";
|
||||
const t = document.getElementById("caption"),
|
||||
n = document.getElementById("autoscroll"),
|
||||
l = setInterval((function() {
|
||||
"0" === n.value.toString() ? (clearInterval(l), e.style.border = "1px solid #292c35") : t.scrollTop = t.scrollHeight
|
||||
}), 1e3)
|
||||
},
|
||||
updateMeetingName = () => {
|
||||
const e = document.getElementById("meeting-name");
|
||||
getMeetingName() ? (e.innerHTML = getMeetingName(), chrome.runtime.sendMessage({
|
||||
type: "meetingName",
|
||||
meetingName: getMeetingName()
|
||||
})) : (e.innerHTML = getDefaultName(), chrome.runtime.sendMessage({
|
||||
type: "meetingName",
|
||||
meetingName: getDefaultName()
|
||||
}))
|
||||
},
|
||||
highlight = e => {
|
||||
let t = document.getElementById("highlight-laxis"),
|
||||
n = document.getElementById("popup"),
|
||||
l = document.getElementById("laxis-miniPanel");
|
||||
weTurnedCaptionsOn ? bookmarkList.forEach(((l, i) => {
|
||||
const o = t.childNodes[i].childNodes[0],
|
||||
d = t.childNodes[i].childNodes[1],
|
||||
a = document.getElementById(`laxis-highlight-${bookmarkList[i].code}-mini`);
|
||||
i === e ? l.enable ? (l.enable = !1, o.style.backgroundColor = "", a.style.backgroundColor = "", d.style.color = "#9e9e9e", clearTimeout(notificationsTimeout), n.classList.remove("show")) : (l.enable = !0, o.style.backgroundColor = "#696969", a.style.backgroundColor = "#696969", d.style.color = "#ffffff", n.innerHTML = "Your conversation is being highlighted.", n.style.backgroundColor = "#454953", n.style.color = l.code, n.classList.add("show"), clearTimeout(notificationsTimeout), notificationsTimeout = setTimeout((() => {
|
||||
n.classList.remove("show")
|
||||
}), notificationsTimeoutDuration)) : (l.enable = !1, o.style.backgroundColor = "", a.style.backgroundColor = "", d.style.color = "#9e9e9e")
|
||||
})) : (l.style.display && alert("Your caption is turned off"), n.style.backgroundColor = "#818388", n.style.color = "#292c35", n.innerHTML = "Your caption is turned off", n.classList.add("show"), clearTimeout(notificationsTimeout), notificationsTimeout = setTimeout((() => {
|
||||
n.classList.remove("show")
|
||||
}), notificationsTimeoutDuration))
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
const stopTranscribing = () => {
|
||||
debug("call stopTranscribing"), notificationsOff(), clearInterval(closedCaptionsAttachInterval), closedCaptionsAttachInterval = null, captionContainerChildObserver.disconnect(), captionContainerAttributeObserver.disconnect()
|
||||
},
|
||||
startTranscribing = () => {
|
||||
debug("call startTranscribing"), currentSessionIndex = null, closedCaptionsAttachInterval = setInterval(tryTo(closedCaptionsAttachLoop, "attach to captions"), 1e3), setCurrentTranscriptDetails()
|
||||
},
|
||||
toggleTranscribing = () => {
|
||||
debug("call toggleTranscribing"), isTranscribing ? stopTranscribing() : startTranscribing(), isTranscribing = !isTranscribing
|
||||
},
|
||||
turnOnCaptions = () => {
|
||||
const t = getElementWithXPathFallback(document, XPATH_TURN_ON_CAPTIONS_BUTTON, XPATH_TURN_ON_CAPTIONS_BUTTON_V20210602);
|
||||
return debug("captionsButtonOn", t), t && (t.click(), notificationsOn()), t
|
||||
},
|
||||
turnOffCaptions = () => {
|
||||
const t = getElementWithXPathFallback(document, XPATH_TURN_OFF_CAPTIONS_BUTTON, XPATH_TURN_OFF_CAPTIONS_BUTTON_V20210602);
|
||||
return debug("captionsButtonOff", t), t && (t.click(), notificationsOff()), t
|
||||
},
|
||||
turnOnCaptionNotificationsOn = () => {
|
||||
const t = document.getElementById("popup");
|
||||
t.style.backgroundColor = "#818388", t.style.color = "#292c35", t.innerHTML = "Please turn on captions", t.classList.add("show"), clearTimeout(notificationsTimeout), notificationsTimeout = setTimeout((() => {
|
||||
t.classList.remove("show")
|
||||
}), 500)
|
||||
},
|
||||
notificationsOn = () => {
|
||||
const t = document.getElementById("laxis-caption-toggle"),
|
||||
n = document.getElementById("laxis-caption-toggle-mini"),
|
||||
i = document.getElementById("captionIcon"),
|
||||
o = document.getElementById("captionIconMini");
|
||||
t.removeChild(i), n.removeChild(o);
|
||||
const e = createCaptionOnIcon(),
|
||||
c = createCaptionOnIcon();
|
||||
e.id = "captionIcon", e.style.width = "15px", e.style.height = "15px", t.appendChild(e), c.id = "captionIconMini", c.style.width = "15px", c.style.height = "15px", n.appendChild(c);
|
||||
const s = document.getElementById("popup");
|
||||
s.style.backgroundColor = "#818388", s.style.color = "#292c35", s.innerHTML = "Your caption is turned on", s.classList.add("show"), clearTimeout(notificationsTimeout), notificationsTimeout = setTimeout((() => {
|
||||
s.classList.remove("show")
|
||||
}), notificationsTimeoutDuration)
|
||||
},
|
||||
notificationsOff = () => {
|
||||
const t = document.getElementById("laxis-caption-toggle"),
|
||||
n = document.getElementById("laxis-caption-toggle-mini"),
|
||||
i = document.getElementById("captionIcon"),
|
||||
o = document.getElementById("captionIconMini");
|
||||
t.removeChild(i), n.removeChild(o);
|
||||
const e = createCaptionOffIcon(),
|
||||
c = createCaptionOffIcon();
|
||||
e.id = "captionIcon", e.style.width = "15px", e.style.height = "15px", t.appendChild(e), c.id = "captionIconMini", c.style.width = "15px", c.style.height = "15px", n.appendChild(c);
|
||||
const s = document.getElementById("popup");
|
||||
s.style.backgroundColor = "#818388", s.style.color = "#292c35", s.innerHTML = "Your caption is turned off", s.classList.add("show"), clearTimeout(notificationsTimeout), notificationsTimeout = setTimeout((() => {
|
||||
s.classList.remove("show")
|
||||
}), notificationsTimeoutDuration)
|
||||
},
|
||||
toggleCaptions = () => {
|
||||
debug("call toggleCaptions"), weTurnedCaptionsOn ? turnOffCaptions() : turnOnCaptions(), weTurnedCaptionsOn = !weTurnedCaptionsOn
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
const findCaptionsContainer = () => {
|
||||
let e = document.querySelector('div[jsname="dsyhDe"]');
|
||||
return e || (e = document.querySelector('div[jscontroller="D1tHje"] > div > div:first-child')), e && (captionContainerChildObserver.observe(e, {
|
||||
childList: !0,
|
||||
subtree: !0,
|
||||
characterData: !0
|
||||
}), captionContainerAttributeObserver.observe(e, {
|
||||
attributes: !0,
|
||||
subtree: !1,
|
||||
attributeOldValue: !0
|
||||
}), Array.from(e.children).forEach(tryTo((e => {
|
||||
updateCurrentTranscriptSession(e)
|
||||
}), "handling child node")), debug("Final CaptionsContainer", e)), e
|
||||
},
|
||||
findCaptionsContainerObsolete = () => {
|
||||
captionContainerChildObserver.disconnect(), captionContainerAttributeObserver.disconnect();
|
||||
const e = {},
|
||||
t = Array.from(document.querySelectorAll("img")).filter((e => e.src.match(/\.googleusercontent\.com\//)));
|
||||
for (let n of t) n.className in e || (e[n.className] = []), e[n.className].push(n);
|
||||
const n = [];
|
||||
for (let t of Object.values(e)) {
|
||||
let e = 0;
|
||||
for (let n of t) {
|
||||
const t = document.evaluate("..//span", n.parentElement, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);
|
||||
let r;
|
||||
for (; r = t.iterateNext();)
|
||||
if (0 === r.children.length && r.textContent.length > 3) {
|
||||
e += 1;
|
||||
break
|
||||
}
|
||||
}
|
||||
if (e !== t.length) continue;
|
||||
let r = null;
|
||||
if (t.length >= 2) {
|
||||
const e = [...t];
|
||||
let n = null,
|
||||
o = !1;
|
||||
do {
|
||||
for (let t in e) {
|
||||
if (!e[t].parent) {
|
||||
o = !0;
|
||||
break
|
||||
}
|
||||
e[t] = e[t].parent, 0 === t ? n = e[t] : n && n !== e[t] && (n = null), debug("current", n)
|
||||
}
|
||||
} while (null === n && !1 === o);
|
||||
r = n
|
||||
} else {
|
||||
let e = t[0];
|
||||
for (; null === r && e;) e.getAttribute("jscontroller") ? r = e : e = e.parentNode
|
||||
}
|
||||
if (r) {
|
||||
const e = r?.firstChild?.firstChild?.tagName;
|
||||
debug("first grand child tag name", e);
|
||||
const t = "IMG" === e ? r : r.firstChild.firstChild;
|
||||
debug("caption container candidate", t), null !== t && n.push(t)
|
||||
}
|
||||
}
|
||||
if (1 === n.length) return captionContainerChildObserver.observe(n[0], {
|
||||
childList: !0,
|
||||
subtree: !0,
|
||||
characterData: !0
|
||||
}), captionContainerAttributeObserver.observe(n[0], {
|
||||
attributes: !0,
|
||||
subtree: !1,
|
||||
attributeOldValue: !0
|
||||
}), Array.from(n[0].children).forEach(tryTo((e => {
|
||||
updateCurrentTranscriptSession(e)
|
||||
}), "handling child node")), debug("Final CaptionsContainer", n[0]), n[0]
|
||||
},
|
||||
captionContainerChildObserver = new MutationObserver(tryTo((e => {
|
||||
for (let t of e)
|
||||
if (debug("mutation target", t.target), t.target === captionsContainer) {
|
||||
debug("update with added nodes");
|
||||
for (let e of t.addedNodes) updateCurrentTranscriptSession(e)
|
||||
} else {
|
||||
const e = Array.from(t.addedNodes).filter((e => "SPAN" === e.nodeName || "#text" === e.nodeName)),
|
||||
n = Array.from(t.removedNodes).filter((e => "SPAN" === e.nodeName || "#text" === e.nodeName));
|
||||
if (debug("addedSpansOrTexts", e), "characterData" === t.type || e.length > 0 || n.length > 0) {
|
||||
let e = t.target;
|
||||
for (; e && e.parentNode !== captionsContainer;) e = e.parentNode;
|
||||
if (!e) {
|
||||
debug("could not find root for", t.target);
|
||||
continue
|
||||
}
|
||||
if (debug("update with parent node"), e.querySelector("button")) {
|
||||
let t = Array.from(e.children),
|
||||
n = t.slice(0, t.length - 1).slice(-4);
|
||||
for (let e of n) updateCurrentTranscriptSession(e)
|
||||
} else updateCurrentTranscriptSession(e)
|
||||
}
|
||||
}
|
||||
}), "executing observer")),
|
||||
captionContainerAttributeObserver = new MutationObserver(tryTo((e => {
|
||||
for (let t of e)
|
||||
if ("style" === t.attributeName) {
|
||||
const e = t.target.getAttribute("style");
|
||||
"display: none;" === t.oldValue && "" === e && (currentSessionIndex = null)
|
||||
}
|
||||
}), "executing observer"));
|
||||
let isCaptionTurnedOn = !1;
|
||||
const closedCaptionsAttachLoop = () => {
|
||||
if (captionsContainer = findCaptionsContainer(), captionsContainer) debug("attached to closed captions"), isCaptionTurnedOn = !0, clearInterval(closedCaptionsAttachInterval);
|
||||
else if (!hasCaptionButtons) {
|
||||
if (isCaptionTurnedOn) return;
|
||||
if (getElementWithXPathFallback(document, XPATH_CAPTION_OPEN_TOAST, XPATH_CAPTION_OPEN_TOAST_V20210602)) return popup.classList.remove("show"), isCaptionTurnedOn = !0, notificationsOn(), void clearInterval(closedCaptionsAttachInterval);
|
||||
isCaptionTurnedOn || turnOnCaptionNotificationsOn()
|
||||
}
|
||||
},
|
||||
removeCaptionPanel = () => {
|
||||
// debug("start remove caption panel");
|
||||
// const e = document.querySelector('div[jscontroller="D1tHje"]');
|
||||
// debug("googleMeetCaptionsPanel", e), e && (e.style = "height: 0px", turnOffCaptions(), setTimeout((() => {
|
||||
// turnOnCaptions()
|
||||
// }), 500))
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
const parents=e=>{const t=[e];for(;e;e=e.parentNode)t.unshift(e);return t},getCommonAncestor=(e,t)=>{const n=parents(e),o=parents(t);if(n[0]===o[0])for(let e=0;e<n.length;e++)if(n[e]!==o[e])return n[e-1]},xpath=(e,t=document)=>document.evaluate(e,t,null,XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue,findButtonContainer=()=>{const e=getElementWithXPathFallback(document,XPATH_MEETING_DETAILS,XPATH_MEETING_DETAILS_V20210602),t=getElementWithXPathFallback(document,XPATH_SELECTOR_CHAT,XPATH_SELECTOR_CHAT_V20210602);return getCommonAncestor(e,t)},getElementWithXPathFallback=(e,t,n)=>xpath(t,e)||xpath(n,e),displayMenu=()=>{document.getElementById("laxis-downloadMenu").style.display="block"},dragElement=e=>{let t=0,n=0,o=0,l=0;function u(e){(e=e||window.event).preventDefault(),o=e.clientX,l=e.clientY,document.onmouseup=c,document.onmousemove=a}function a(u){(u=u||window.event).preventDefault(),t=o-u.clientX,n=l-u.clientY,o=u.clientX,l=u.clientY,e.style.top=e.offsetTop-n+"px",e.style.left=e.offsetLeft-t+"px",currentTop=e.style.top,currentRight=e.style.right}function c(){document.onmouseup=null,document.onmousemove=null}document.getElementsByName(e.id+"Header")?document.getElementsByName(e.id+"Header").forEach((e=>{e.onmousedown=u})):e.onmousedown=u};
|
||||
@@ -0,0 +1 @@
|
||||
window.__gmt_get=t=>get(`setting.${t}`),window.__gmt_set=(t,e)=>{set(`setting.${t}`,e),syncSettings()},window.__gmt_remove=t=>{remove(`setting.${t}`),syncSettings()};const syncSettings=()=>{TRANSCRIPT_FORMAT_MEETING=getOrSet("setting.transcript-format-meeting","# $year$-$month$-$day$ $name$\n\n$text$"),TRANSCRIPT_FORMAT_SESSION_JOIN=getOrSet("setting.transcript-format-session-join","\n\n...\n\n"),TRANSCRIPT_FORMAT_SPEAKER=getOrSet("setting.transcript-format-speaker","$hour$:$minute$:$second$\n $name$: $text$"),TRANSCRIPT_FORMAT_SPEAKER_JOIN=getOrSet("setting.transcript-format-speaker-join","\n\n"),SPEAKER_NAME_MAP=getOrSet("setting.speaker-name-map",{}),DEBUG=getOrSet("setting.debug",!1)};
|
||||
@@ -0,0 +1 @@
|
||||
const makeFullKey=e=>`__laxis_${e}`,makeTranscriptKey=(...e)=>{const[t,r,o]=e,n=[`hangout_${t}`];return e.length>=2&&(n.push(`session_${r}`),e.length>=3&&n.push(`speaker_${o}`)),n.join("_")},get=e=>{const t=window.localStorage.getItem(makeFullKey(e));return"string"==typeof t||t instanceof String?(debug(e,t),JSON.parse(t)):t},set=(e,t,r=!1)=>{const o=makeFullKey(e),n=JSON.stringify(t),a=makeFullKey("rotationKeys");let s=JSON.parse(window.localStorage.getItem(a))||[],i=new Set(s);for(;;)try{window.localStorage.setItem(o,n),r&&(i.add(o),window.localStorage.setItem(a,JSON.stringify(Array.from(i))));break}catch(e){if(e instanceof DOMException&&("QuotaExceededError"===e.name||e.code===DOMException.QUOTA_EXCEEDED_ERR)){if(console.log("Local storage quota exceeded! Deleting old keys to make room for new ones. This may take a while..."),debug("Local storage quota exceeded! Deleting old keys to make room for new ones. This may take a while..."),i.size>0){for(let e=0;e<5&&i.size>0;e++){const e=i.values().next().value;i.delete(e),window.localStorage.removeItem(e)}window.localStorage.setItem(a,JSON.stringify(Array.from(i)));continue}console.error("No keys available to delete.");break}console.error("Unexpected error:",e);break}},remove=e=>{debug(`remove ${makeFullKey(e)}`),window.localStorage.removeItem(makeFullKey(e))},getOrSet=(e,t)=>{const r=get(e);return null==r?(set(e,t),t):r},increment=e=>{const t=get(e);if(null==t)return set(e,0),0;{let r=t+1;return set(e,r),r}},setSpeaker=e=>{set(makeTranscriptKey(e.transcriptId,e.sessionIndex,e.speakerIndex),{image:e.image,person:e.person,text:e.text,startedAt:e.startedAt,endedAt:e.endedAt,highlight:e.highlight},!0)},getTranscript=e=>{const t=get(makeTranscriptKey(e))||0;let r=[];const o=get(makeTranscriptKey(e,t))||0;for(let n=0;n<=o;n+=1){const o=get(makeTranscriptKey(e,t,n));if(o&&o.text&&o.text.match(/\S/g)){startTimeStored||(startTimeStored=new Date(o.startedAt),startTime=new Date(o.startedAt));const a={transcriptId:e,sessionIndex:t,speakerIndex:n,person:o.person in SPEAKER_NAME_MAP?SPEAKER_NAME_MAP[o.person]:o.person,startedAt:new Date(o.startedAt),endedAt:new Date(o.endedAt),image:o.image,text:o.text,highlight:o.highlight};r.push(a)}}return r},deleteTranscript=e=>{const t=get(makeTranscriptKey(e));for(let r=0;r<=t;r+=1){const t=get(makeTranscriptKey(e,r));for(let o=0;o<=t;o+=1)remove(makeTranscriptKey(e,r,o));remove(makeTranscriptKey(e,r))}remove(makeTranscriptKey(e)),get(makeTranscriptKey(`${e}_name`))&&remove(makeTranscriptKey(`${e}_name`));let r=get(KEY_TRANSCRIPT_IDS)||[],o=get(APPLICATION_SPEECH_IDS)||[];const n=r.indexOf(e),a=o.findIndex((t=>t.ext===e));r.splice(n,1),o.splice(a,1),debug("would set transcript to",r),debug("would set transcript pairs to",o),set(KEY_TRANSCRIPT_IDS,r),set(APPLICATION_SPEECH_IDS,o);const s=document.querySelector(`#${e}`);if(s){const e=s.parentNode;e.removeChild(s),0===e.children.length&&(e.parentNode.removeChild(e.previousSibling),e.parentNode.removeChild(e))}else debug(`transcriptNode doesn't exist for ${e}`)},deletePreviousTranscripts=()=>{let e=get(KEY_TRANSCRIPT_IDS)||[];if(e.length>1)for(let t of e)t!==currentTranscriptId&&deleteTranscript(t)};
|
||||
@@ -0,0 +1,289 @@
|
||||
const getTranscriptText = (e, t) => {
|
||||
let o = [];
|
||||
sessionList.forEach((e => {
|
||||
o.unshift(e)
|
||||
}));
|
||||
let n = "<div>",
|
||||
i = {};
|
||||
n += "<div style='font-size:20pt; color: #2F5496'>Transcripts<br></div>", o.forEach(((s, a) => {
|
||||
if (s.text && s.text.length && (0 === a || s.startedAt !== o[a - 1].startedAt)) {
|
||||
let o = "",
|
||||
a = "You" === s.person ? appUser : s.person;
|
||||
if (s.highlight.length && e && (o = s.highlight[0], s.highlight[0])) {
|
||||
let e = s.highlight[0],
|
||||
t = i[e] ? i[e] : [];
|
||||
t.push({
|
||||
text: s.text,
|
||||
time: getTimeStr(startTime, s.startedAt),
|
||||
person: a
|
||||
}), i[e] = t
|
||||
}
|
||||
if (n += "<div style='font-size:11pt'>", n = n + "<i style='color:#9e9e9e'>" + a, t && (n += ` (${getTimeStr(startTime,s.startedAt)})`), n += ": </i>", e && o.length) {
|
||||
const e = bookmarkList.find((e => e.color === o)).code;
|
||||
n += `<div style="color:${e}; display:inline">`
|
||||
}
|
||||
n += s.text, e && o.length && (n += "</div>"), n += "<br><br></div>"
|
||||
}
|
||||
})), n += "</div>";
|
||||
let s = "";
|
||||
return e && (s += "<div>", s += "<div style='font-size:20pt; color: #2F5496'>Highlights<br></div>", Object.keys(i).forEach((e => {
|
||||
let t = bookmarkList.find((t => t.color === e)),
|
||||
o = t.name,
|
||||
n = t.code;
|
||||
s += "<div>", s += `<span style="color:${n}; font-weight:bold; font-size:14pt">${o}:<br></span>`, i[e].forEach((e => {
|
||||
s += `<div style='font-size:11pt'><i style='color:#9e9e9e'>${e.person} (${e.time}): </i>${e.text}<br><br></div>`
|
||||
})), s += "</div>"
|
||||
})), s += "</div>"), `<div style='font-family:calibri'><div style='font-size:14pt'>${startTime}<br></div>` + s + n + "</div>"
|
||||
},
|
||||
Export2Txt = (e, t = "") => {
|
||||
let o = document.createElement("a"),
|
||||
n = e.replaceAll("<br>", "\n"),
|
||||
i = document.createElement("div");
|
||||
i.style.display = "none", i.innerHTML = n;
|
||||
let s = i.innerText;
|
||||
const a = new File([s], "filename"),
|
||||
d = URL.createObjectURL(a);
|
||||
return o.href = d, o.download = `${t}.txt`, document.body.appendChild(o), o.click(), document.body.removeChild(o), Promise.resolve(1)
|
||||
},
|
||||
Export2Pdf = (e, t = "") => {
|
||||
const o = window.html2pdf;
|
||||
t = t ? t + ".pdf" : `${getDefaultName()}.pdf`;
|
||||
let n = document.createElement("div");
|
||||
n.innerHTML = e, document.body.appendChild(n);
|
||||
const i = {
|
||||
margin: [8, 16, 8, 16],
|
||||
filename: `${t}.pdf`,
|
||||
enableLinks: !1,
|
||||
pagebreak: {
|
||||
avoid: ["div"],
|
||||
mode: ["css"]
|
||||
},
|
||||
image: {
|
||||
type: "jpeg",
|
||||
quality: 1
|
||||
},
|
||||
html2canvas: {
|
||||
allowTaint: !0,
|
||||
dpi: 144,
|
||||
letterRendering: !0,
|
||||
logging: !1,
|
||||
scale: 2,
|
||||
scrollX: 0,
|
||||
scrollY: 0
|
||||
}
|
||||
};
|
||||
return new Promise(((e, t) => {
|
||||
o().from(n).set(i).toPdf().get("pdf").then((e => {
|
||||
const t = e.internal.getNumberOfPages();
|
||||
for (let o = 1; o < t + 1; o++) e.setPage(o), e.setFontSize(14), e.text(`${o}/${t}`, e.internal.pageSize.getWidth() - 10, e.internal.pageSize.getHeight() - 5);
|
||||
document.body.removeChild(n)
|
||||
})).save().then((() => {
|
||||
e("Downloaded")
|
||||
})).catch((e => t(e)))
|
||||
}))
|
||||
},
|
||||
Export2Word = (e, t = "") => {
|
||||
var o = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>Export HTML To Doc</title></head><body>" + e + "</body></html>",
|
||||
n = new Blob(["\ufeff", o], {
|
||||
type: "application/msword"
|
||||
}),
|
||||
i = "data:application/vnd.ms-word;charset=utf-8," + encodeURIComponent(o);
|
||||
t = t ? `${t}.doc` : `${getDefaultName()}.doc`;
|
||||
var s = document.createElement("a");
|
||||
return document.body.appendChild(s), navigator.msSaveOrOpenBlob ? navigator.msSaveOrOpenBlob(n, t) : (s.href = i, s.download = t, s.click()), document.body.removeChild(s), Promise.resolve(1)
|
||||
},
|
||||
disableSaveToLaxisCloud = e => {
|
||||
const t = document.getElementById("laxis-confirm-download");
|
||||
t && (t.disabled = e, t.style.color = t.disabled ? "#999" : "");
|
||||
const o = document.getElementById("extension");
|
||||
o && (o.onchange = () => {
|
||||
t && (t.disabled = e && "app" === o.value, t.style.color = t.disabled ? "#999" : "")
|
||||
});
|
||||
const n = document.getElementById("autoSaveCheck");
|
||||
n && (n.disabled = e)
|
||||
},
|
||||
displayGoogleMeetQuota = (e, t) => {
|
||||
const o = document.getElementById("google-meet-quota");
|
||||
o && -1 !== t && 0 !== t && (e < t ? (o.innerHTML = `Autosave to Laxis cloud: ${e.toFixed(0)} / ${t.toFixed(0)} minutes. <br/> Please <span style='color: #2196f3;'>upgrade</span> to enjoy unlimited autosave.`, disableSaveToLaxisCloud(!1)) : (o.innerHTML = `Autosave to Laxis cloud: <span style='color: #E94B4B;'>${e.toFixed(0)} / ${t.toFixed(0)}</span> minutes. <br/> Please <span style='color: #2196f3;'>upgrade</span> to enjoy unlimited autosave.`, disableSaveToLaxisCloud(!0)), o.style.display = "block")
|
||||
},
|
||||
reDisplayPrompt = () => {
|
||||
const e = document.getElementById("login-prompt");
|
||||
e && (e.style.display = "block"), chrome.storage.local.remove("token")
|
||||
},
|
||||
addRemindLogin = () => {
|
||||
console.log("add login");
|
||||
const e = document.getElementById("laxis-miniPanel"),
|
||||
t = document.getElementById("laxis-expandPanel"),
|
||||
o = document.createElement("div");
|
||||
o.title = "Login to autosave notes", o.id = "laxis-remindLogin", o.style.width = "40px", o.style.height = "40px", o.classList.add("miniButtonContainer"), o.style.border = "0", o.addEventListener("click", signup), o.style.padding = "0";
|
||||
const n = createRemindLoginIcon();
|
||||
n.id = "remindLoginIcon", o.appendChild(n), n.id = "remindLoginIcon", o.style.display = "none", e.insertBefore(o, t)
|
||||
},
|
||||
reDisplayRemindLogin = () => {
|
||||
console.log("redisplay");
|
||||
const e = document.getElementById("laxis-remindLogin");
|
||||
e && (e.style.display = "block"), chrome.storage.local.remove("token")
|
||||
},
|
||||
getTopics = (e, t) => window.fetch(`${domainUrl}/api/v2/templates/${e}/topics`, t),
|
||||
Export2App = async (e, t = !1) => new Promise(((o, n) => {
|
||||
chrome.storage.local.get(["token"], (function(i) {
|
||||
if (i.token) {
|
||||
let s = document.getElementById("laxis-openDownloadMenu"),
|
||||
a = document.getElementById("laxis-download-menu-mini");
|
||||
s.classList.add("loading"), a.classList.add("loading"), window.fetch(`${domainUrl}/api/v2/templates?quick-note=true`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${i.token}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then((s => {
|
||||
200 === s.status ? s.json().then((s => {
|
||||
s.items.length ? getTopics(s.items[0].id, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${i.token}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then((a => {
|
||||
a.json().then((a => {
|
||||
const d = a.items;
|
||||
let l = [];
|
||||
if (getTranscript(currentTranscriptId).forEach((({
|
||||
image: t,
|
||||
person: o,
|
||||
text: n,
|
||||
startedAt: i,
|
||||
endedAt: s,
|
||||
highlight: a
|
||||
}) => {
|
||||
let r = [];
|
||||
if (a && e) {
|
||||
const e = bookmarkList.find((e => e.color === a[0]));
|
||||
if (e) {
|
||||
const t = d.find((t => t.color.toLowerCase() === e.code.toLowerCase()));
|
||||
t && (r = [t.id])
|
||||
}
|
||||
}
|
||||
n && l.push({
|
||||
imageUrl: t,
|
||||
person: "You" === o ? appUser : o,
|
||||
startedAt: i,
|
||||
endedAt: s || i,
|
||||
highlights: r,
|
||||
text: n
|
||||
})
|
||||
})), l.length) {
|
||||
let e = JSON.stringify({
|
||||
meetingId: currentTranscriptId,
|
||||
meetingName: document.getElementById("meeting-name").innerText,
|
||||
templateId: s.items[0].id,
|
||||
transcripts: l,
|
||||
isEnded: t
|
||||
}),
|
||||
a = {
|
||||
Authorization: `Bearer ${i.token}`,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
d = get(APPLICATION_SPEECH_IDS) || [],
|
||||
r = d.findIndex((e => e.ext === currentTranscriptId));
|
||||
if (-1 === r) window.fetch(`${domainUrl}/api/v1/speeches/google-meet`, {
|
||||
method: "POST",
|
||||
headers: a,
|
||||
body: e
|
||||
}).then((e => {
|
||||
200 === e.status ? e.json().then((e => {
|
||||
set(APPLICATION_SPEECH_IDS, [...d, {
|
||||
ext: currentTranscriptId,
|
||||
app: e.id
|
||||
}]), chrome.runtime.sendMessage({
|
||||
type: "transcriptId",
|
||||
transcriptId: e.id
|
||||
}), o(e.id)
|
||||
})) : 401 === e.status ? (e.json().then((e => {
|
||||
saveLog(`Export2App 401 fail 1 ${i.token} ${e.id} ${e.message}`)
|
||||
})), reDisplayPrompt(), n("Expired token")) : e.json().then((e => {
|
||||
n(e.message)
|
||||
}))
|
||||
})).catch((e => {
|
||||
n(e)
|
||||
}));
|
||||
else {
|
||||
let t = d[r].app;
|
||||
window.fetch(`${domainUrl}/api/v1/speeches/google-meet/${t}`, {
|
||||
method: "PUT",
|
||||
headers: a,
|
||||
body: e
|
||||
}).then((e => {
|
||||
200 === e.status ? e.json().then((() => {
|
||||
o(t)
|
||||
})) : 401 === e.status ? (e.json().then((e => {
|
||||
saveLog(`Export2App 401 fail 2 ${i.token} ${t} ${e.message}`)
|
||||
})), reDisplayPrompt(), n("Expired token")) : e.json().then((e => {
|
||||
n(e.message)
|
||||
}))
|
||||
})).catch((e => {
|
||||
n(e)
|
||||
}))
|
||||
}
|
||||
} else n("Empty transcript")
|
||||
}))
|
||||
})).catch((e => n(e))) : n("No quick note template")
|
||||
})) : 401 === s.status ? (s.json().then((e => {
|
||||
saveLog(`Export2App 401 fail 3 ${e.message} ${i.token}`)
|
||||
})), reDisplayPrompt(), n("Expired token")) : s.json().then((e => n(e.message)))
|
||||
})).catch((e => {
|
||||
n(e)
|
||||
})).finally((() => {
|
||||
s.classList.remove("loading"), a.classList.remove("loading"), s.classList.add("finishing"), a.classList.add("finishing"), setTimeout((() => {
|
||||
s.classList.remove("finishing"), a.classList.remove("finishing")
|
||||
}), 3e3)
|
||||
}))
|
||||
} else n("Not logged in")
|
||||
}))
|
||||
})), downloadTranscript = () => {
|
||||
const e = document.getElementById("meeting-name").innerText,
|
||||
t = document.getElementById("laxis-confirm-download");
|
||||
t.innerHTML = "Downloading...";
|
||||
const o = document.getElementById("highlightCheck").checked,
|
||||
n = document.getElementById("timestampCheck").checked,
|
||||
i = document.getElementById("extension").value.toString(),
|
||||
s = getTranscriptText(o, n);
|
||||
let a;
|
||||
switch (i) {
|
||||
case "pdf":
|
||||
a = () => Export2Pdf(s, e);
|
||||
break;
|
||||
case "doc":
|
||||
a = () => Export2Word(s, e);
|
||||
break;
|
||||
case "txt":
|
||||
a = () => Export2Txt(s, e);
|
||||
break;
|
||||
default:
|
||||
a = () => Export2App(o)
|
||||
}
|
||||
a().then((e => {
|
||||
"app" === i && window.open(`${domainUrl}/transcript/${e}`, "_blank")
|
||||
})).catch((e => {
|
||||
window.alert(e);
|
||||
const t = get(ERROR_SAVING) || [];
|
||||
set(ERROR_SAVING, [...t, e])
|
||||
})).finally((() => {
|
||||
t.innerHTML = "Download";
|
||||
const e = document.getElementById("laxis-downloadMenu");
|
||||
e && (e.style.display = "none")
|
||||
}))
|
||||
};
|
||||
|
||||
function saveLog(e) {
|
||||
const t = {
|
||||
message: e,
|
||||
time: (new Date).toISOString()
|
||||
};
|
||||
chrome.storage.local.get("logs", (function(e) {
|
||||
var o = structuredClone(e.logs);
|
||||
void 0 === o ? o = [t] : o.push(t), chrome.storage.local.set({
|
||||
logs: o
|
||||
})
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.stringSimilarity=e():t.stringSimilarity=e()}(self,(function(){return t={138:t=>{function e(t,e){if((t=t.replace(/\s+/g,""))===(e=e.replace(/\s+/g,"")))return 1;if(t.length<2||e.length<2)return 0;let r=new Map;for(let e=0;e<t.length-1;e++){const n=t.substring(e,e+2),o=r.has(n)?r.get(n)+1:1;r.set(n,o)}let n=0;for(let t=0;t<e.length-1;t++){const o=e.substring(t,t+2),s=r.has(o)?r.get(o):0;s>0&&(r.set(o,s-1),n++)}return 2*n/(t.length+e.length-2)}t.exports={compareTwoStrings:e,findBestMatch:function(t,r){if(!function(t,e){return"string"==typeof t&&!!Array.isArray(e)&&!!e.length&&!e.find((function(t){return"string"!=typeof t}))}(t,r))throw new Error("Bad arguments: First argument should be a string, second should be an array of strings");const n=[];let o=0;for(let s=0;s<r.length;s++){const i=r[s],f=e(t,i);n.push({target:i,rating:f}),f>n[o].rating&&(o=s)}return{ratings:n,bestMatch:n[o],bestMatchIndex:o}}}}},e={},function r(n){if(e[n])return e[n].exports;var o=e[n]={exports:{}};return t[n](o,o.exports,r),o.exports}(138);var t,e}));
|
||||
@@ -0,0 +1 @@
|
||||
const pad=t=>t<10?`0${t}`:t,debug=(...t)=>{DEBUG&&console.log("[laxis debug]",...t)},tryTo=(t,e)=>async(...n)=>{try{return await t(...n)}catch(t){console.error(`error ${e}:`,t)}},getTimeStr=(t,e)=>{const n=new Date(e)-t,o=Math.trunc(n/1e3)%60,a=Math.trunc(n/1e3/60);return pad(a)+":"+pad(o)},signup=()=>{window.open(loginUrl)},upgrade=()=>{window.open(upgradeUrl)},getDefaultName=()=>{const t=new Date,e=t.getDate(),n=t.getMonth(),o=t.getFullYear(),a=t.getHours(),r=t.getMinutes();return`Meeting_${o}${pad(n+1)}${pad(e)}_${pad(a)}${pad(r)}`};function onShortcut(t){var e="";document.addEventListener("keydown",(function(n){if(n.shiftKey){if((e+=""+n.key)===SHORTCUT_KEY)return t();SHORTCUT_KEY.indexOf(e)&&(e=""+n.key)}}))}onShortcut((function(){const t=document.getElementById("laxis-repair");t&&(t.style.display="block")}));const getCurrentLanguage=()=>{const t=xpath('//button[contains(@jsname,"gnzhTe")]',document);if(t){const e=t.ariaLabel;return e.substring(0,e.length-17)}return""},getToSettings=()=>{const t='//div[contains(@jscontroller,"bZ0mod")]';if(xpath('//div[contains(@aria-label, "Settings")]',document)){const t=xpath('//button[contains(@aria-label, "Close dialog")]',document);if(t)return void t.click()}else{const e=xpath('//button[contains(@aria-label, "More options") and contains(@jscontroller, "PIVayb")]',document);e&&e.click();let n=!1;const o=xpath(t,document);if(o&&"none"!==o?.style?.display&&(o.style.display="none",n=!0),!n){let e=setInterval((()=>{const n=xpath(t,document);n&&"none"!==n?.style?.display&&(n.style.display="none",clearInterval(e))}),[50])}let a=setInterval((()=>{const t=xpath('//i[contains(text(), "settings")]',document);if(t){t.click(),clearInterval(a);let e=setInterval((()=>{const t=xpath('//button[contains(@aria-label, "Captions")]',document);t&&(t.click(),clearInterval(e))}),[50])}}),[50])}},extractLocalStorage=()=>{const t=(t,e="")=>{let n=document.createElement("a");const o=new File([t],"filename"),a=URL.createObjectURL(o);return n.href=a,n.download=`${e}.json`,document.body.appendChild(n),n.click(),document.body.removeChild(n),Promise.resolve(1)};console.log((()=>{var e,n={};for(e in window.localStorage)e.includes("laxis")&&(value=window.localStorage.getItem(e),n[e]=value);if(!chrome||!chrome.storage)return t(JSON.stringify(n),"dumplocalStorage"),n;chrome.storage.local.get(null,(function(e){for(var o in e)return n[o]=e[o],t(JSON.stringify(n),"dumplocalStorage"),n}))})())};
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="18" viewBox="0 0 15 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416" fill="#E94B4B"/>
|
||||
<path d="M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416" stroke="#E94B4B" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="18" viewBox="0 0 15 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416" fill="#FFD339"/>
|
||||
<path d="M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416" stroke="#FFD339" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="18" viewBox="0 0 15 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416" fill="#9CCC65"/>
|
||||
<path d="M1 11.8277H13.5105C13.682 11.8277 13.8499 11.7788 13.9945 11.6867C14.1392 11.5947 14.2546 11.4634 14.3274 11.3081C14.4001 11.1528 14.427 10.98 14.4051 10.81C14.3832 10.64 14.3132 10.4797 14.2035 10.3479L10.9254 6.41387L14.2035 2.47979C14.3132 2.34805 14.3832 2.18778 14.4051 2.01773C14.427 1.84769 14.4001 1.67492 14.3274 1.51965C14.2546 1.36438 14.1392 1.23304 13.9945 1.14101C13.8499 1.04898 13.682 1.00006 13.5105 1H1V17.2416" stroke="#9CCC65" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="160" width="160" version="1.0">
|
||||
<g fill="#fff">
|
||||
<path d="m80 15c-35.88 0-65 29.12-65 65s29.12 65 65 65 65-29.12 65-65-29.12-65-65-65zm0 10c30.36 0 55 24.64 55 55s-24.64 55-55 55-55-24.64-55-55 24.64-55 55-55z"/>
|
||||
<path d="m57.373 18.231a9.3834 9.1153 0 1 1 -18.767 0 9.3834 9.1153 0 1 1 18.767 0z" transform="matrix(1.1989 0 0 1.2342 21.214 28.75)"/>
|
||||
<path d="m90.665 110.96c-0.069 2.73 1.211 3.5 4.327 3.82l5.008 0.1v5.12h-39.073v-5.12l5.503-0.1c3.291-0.1 4.082-1.38 4.327-3.82v-30.813c0.035-4.879-6.296-4.113-10.757-3.968v-5.074l30.665-1.105"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 669 B |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 9.7 KiB |
|
After Width: | Height: | Size: 667 B |
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg fill="#9e9e9e" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 81.159 81.159" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M74.175,5.262c-1.068-1.069-2.527-1.659-4.107-1.659c-1.775,0-3.563,0.748-4.934,2.058l-5.884,4.776l-0.229,0.208
|
||||
c-1.301,1.299-1.813,3.124-1.498,4.962L43.806,28.926l-2.302-1.992c1.65-6.453,1.447-15.049-4.118-20.614
|
||||
C33.311,2.245,28.097,0,22.706,0c-2.143,0-4.261,0.358-6.296,1.063c-0.932,0.323-1.646,1.083-1.909,2.034s-0.042,1.969,0.59,2.726
|
||||
l7.393,8.836l-0.364,5.683l-6.144,0.701l-8.771-7.338c-0.757-0.634-1.776-0.855-2.727-0.59c-0.951,0.264-1.711,0.978-2.034,1.91
|
||||
c-2.396,6.919-0.285,14.371,5.792,20.444c3.274,3.276,7.988,5.007,13.63,5.007c0.413,0,0.829-0.021,1.245-0.041
|
||||
c-0.209,0.182-0.419,0.362-0.617,0.56L5.641,57.847c-4.679,4.679-4.679,12.291,0,16.97l2.827,2.827
|
||||
c2.266,2.267,5.28,3.515,8.485,3.515s6.219-1.248,8.485-3.515L42.29,60.791c0.934-0.933,1.669-1.982,2.223-3.103l14.969,17.296
|
||||
c0.048,0.055,0.098,0.107,0.148,0.159c1.678,1.677,4.196,2.601,7.094,2.601c3.521,0,7.095-1.385,9.326-3.615
|
||||
c3.922-3.922,5.158-12.267,1.012-16.416c-0.053-0.051-0.104-0.101-0.159-0.146L55.108,38.709L67.682,25.76
|
||||
c0.346,0.059,0.692,0.088,1.041,0.088c1.506,0,2.896-0.563,3.916-1.582l4.984-6.113c1.141-1.192,1.855-2.695,2.021-4.251
|
||||
c0.193-1.822-0.398-3.568-1.621-4.79L74.175,5.262z M38.048,56.549L21.196,73.402c-1.133,1.133-2.64,1.757-4.243,1.757
|
||||
s-3.109-0.624-4.243-1.757l-2.827-2.827c-2.339-2.34-2.339-6.146,0-8.484l16.853-16.854c1.133-1.133,2.64-1.757,4.242-1.757
|
||||
c0.471,0,0.932,0.062,1.379,0.165l7.438,8.595C39.813,53.797,39.234,55.363,38.048,56.549z M71.806,69.887
|
||||
c-1.111,1.111-3.154,1.857-5.084,1.857c-1.211,0-2.246-0.293-2.793-0.789L32.121,34.203c-0.797-0.921-2.073-1.269-3.225-0.88
|
||||
c-2.24,0.754-4.671,1.152-7.031,1.152c-3.958,0-7.292-1.154-9.387-3.25c-1.896-1.896-4.559-5.25-4.957-9.432l5.59,4.677
|
||||
c0.63,0.528,1.447,0.773,2.266,0.68l9.915-1.132c1.443-0.165,2.561-1.339,2.653-2.789l0.601-9.384
|
||||
c0.049-0.769-0.199-1.526-0.693-2.116l-4.789-5.724c3.663,0.1,7.229,1.709,10.077,4.557c3.918,3.919,3.833,11.259,2.095,16.421
|
||||
c-0.389,1.154-0.041,2.429,0.88,3.226l36.757,31.808C74.206,63.476,73.891,67.801,71.806,69.887z M73.261,14.035l-4.707,5.772
|
||||
c-0.049-0.019-0.104-0.045-0.164-0.081l-2.037-1.21l-15.787,16.26l-2.207-1.909l16.41-15.935l-1.209-2.035
|
||||
c-0.035-0.062-0.063-0.117-0.08-0.165l5.541-4.499l0.23-0.208c0.299-0.297,0.598-0.4,0.784-0.417l3.646,3.646
|
||||
C73.669,13.404,73.572,13.723,73.261,14.035z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1 @@
|
||||
const script=document.createElement("script");script.setAttribute("type","text/javascript");const filePath=chrome.runtime.getURL("runtime.js");script.setAttribute("src",filePath),document.body.appendChild(script);
|
||||
@@ -0,0 +1,127 @@
|
||||
try {
|
||||
importScripts("config/share.js"), importScripts("config/panel.js")
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
let meetingId = null,
|
||||
transcriptId = null,
|
||||
meetingName = null,
|
||||
username = "";
|
||||
chrome.storage.session.setAccessLevel({
|
||||
accessLevel: "TRUSTED_AND_UNTRUSTED_CONTEXTS"
|
||||
});
|
||||
const getTopics = (e, t) => fetch(`${domainUrl}/api/v2/templates/${e}/topics`, t);
|
||||
chrome.tabs.onRemoved.addListener(((e, t) => {
|
||||
chrome.storage.session.get(["sessionList"], (e => {
|
||||
if (e.sessionList) {
|
||||
let t = e.sessionList;
|
||||
console.log(t), chrome.storage.local.get(["token"], (e => {
|
||||
if (e.token) {
|
||||
let o = e.token;
|
||||
fetch(`${domainUrl}/api/v2/templates?quick-note=true`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${o}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then((e => {
|
||||
200 === e.status ? e.json().then((e => {
|
||||
e.items.length ? getTopics(e.items[0].id, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${o}`,
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
}).then((s => {
|
||||
s.json().then((s => {
|
||||
const n = s.items;
|
||||
let r = [];
|
||||
if (t.forEach((({
|
||||
image: e,
|
||||
person: t,
|
||||
text: o,
|
||||
startedAt: s,
|
||||
endedAt: i,
|
||||
highlight: a
|
||||
}) => {
|
||||
let c = [];
|
||||
if (a) {
|
||||
const e = bookmarkList.find((e => e.color === a[0]));
|
||||
if (e) {
|
||||
const t = n.find((t => t.color.toLowerCase() === e.code.toLowerCase()));
|
||||
t && (c = [t.id])
|
||||
}
|
||||
}
|
||||
o && r.push({
|
||||
imageUrl: e,
|
||||
person: "You" === t ? username : t,
|
||||
startedAt: s,
|
||||
endedAt: i || s,
|
||||
highlights: c,
|
||||
text: o
|
||||
})
|
||||
})), r.length) {
|
||||
let t = JSON.stringify({
|
||||
meetingId: meetingId,
|
||||
meetingName: meetingName,
|
||||
templateId: e.items[0].id,
|
||||
transcripts: r,
|
||||
isEnded: !0
|
||||
}),
|
||||
s = {
|
||||
Authorization: `Bearer ${o}`,
|
||||
"Content-Type": "application/json"
|
||||
};
|
||||
transcriptId ? fetch(`${domainUrl}/api/v1/speeches/google-meet/${transcriptId}`, {
|
||||
method: "PUT",
|
||||
headers: s,
|
||||
body: t
|
||||
}).then((e => {
|
||||
200 === e.status ? (console.log("save success"), chrome.tabs.create({
|
||||
url: `${domainUrl}/transcript/${transcriptId}`
|
||||
})) : 401 === e.status ? console.log("Expired token") : e.json().then((e => {
|
||||
console.log(e.message)
|
||||
}))
|
||||
})).catch((e => {
|
||||
console.log(e)
|
||||
})) : fetch(`${domainUrl}/api/v1/speeches/google-meet`, {
|
||||
method: "POST",
|
||||
headers: s,
|
||||
body: t
|
||||
}).then((e => {
|
||||
200 === e.status ? (chrome.tabs.create({
|
||||
url: `${domainUrl}/transcript/${transcriptId}`
|
||||
}), console.log("success")) : 401 === e.status ? console.log("Expired token") : e.json().then((e => {
|
||||
console.log(e.message)
|
||||
}))
|
||||
})).catch((e => {
|
||||
console.log(e)
|
||||
}))
|
||||
} else console.log("Empty transcript")
|
||||
}))
|
||||
})).catch((e => reject(e))) : console.log("No quick note template")
|
||||
})) : 401 === e.status ? console.log("Expired token") : e.json().then((e => console.log(e.message)))
|
||||
}))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}))
|
||||
})), chrome.runtime.onMessage.addListener((e => {
|
||||
console.log(e), "meetingId" === e.type && (console.log(e.meetingId), meetingId = e.meetingId), "transcriptId" === e.type && (console.log(e.transcriptId), transcriptId = e.transcriptId), "meetingName" === e.type && (meetingName = e.meetingName), "username" === e.type && (username = e.data)
|
||||
})), chrome.runtime.onInstalled.addListener((e => {
|
||||
// e.reason === chrome.runtime.OnInstalledReason.INSTALL && chrome.tabs.create({
|
||||
// url: signupUrl
|
||||
// }, (function(e) {}))
|
||||
})), chrome.runtime.onMessageExternal.addListener((function(e, t, o) {
|
||||
t.origin === domainUrl && (o("extension received the token"), chrome.storage.local.set({
|
||||
token: e.token,
|
||||
refreshToken: e.refreshToken
|
||||
}, (function() {})), chrome.tabs.query({
|
||||
url: googleMeetUrl
|
||||
}, (function(t) {
|
||||
t[0] && chrome.tabs.sendMessage(t[0].id, {
|
||||
token: e.token,
|
||||
refreshToken: e.refreshToken
|
||||
}, (function(e) {}))
|
||||
})))
|
||||
})), chrome.runtime.setUninstallURL("https://www.laxis.tech/uninstall-survey");
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"update_url": "https://clients2.google.com/service/update2/crx",
|
||||
|
||||
"manifest_version": 3,
|
||||
"name": "Google Meet Transcripts & AI Summary",
|
||||
"description": "Google Meet Transcription, AI Summary and Insight. Get the most out of Google Meet!",
|
||||
"version": "4.3.12",
|
||||
"icons": {
|
||||
"16": "image/logo16x16.png",
|
||||
"48": "image/logo48x48.png",
|
||||
"128": "image/logo128x128.png"
|
||||
},
|
||||
"background": {
|
||||
"service_worker": "login.js"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://meet.google.com/*"],
|
||||
"js": [
|
||||
"feature/utilities/packages/jquery.min.js",
|
||||
"feature/utilities/packages/html2canvas.js",
|
||||
"feature/utilities/packages/html2pdf.bundle.min.js",
|
||||
"feature/utilities/packages/string-similarity.min.js",
|
||||
"feature/utilities/util.js",
|
||||
"inject.js",
|
||||
"style/panel.js",
|
||||
"config/share.js",
|
||||
"config/panel.js",
|
||||
"config/record.js",
|
||||
"feature/record/captionObserver.js",
|
||||
"feature/record/captionControls.js",
|
||||
"feature/record/captionProcessing.js",
|
||||
"feature/record/settings.js",
|
||||
"feature/record/storage.js",
|
||||
"feature/record/dom.js",
|
||||
"feature/record/transcript.js",
|
||||
"feature/record/meetingInfo.js",
|
||||
"feature/panel/main.js",
|
||||
"feature/panel/icons.js",
|
||||
"runtime.js"
|
||||
],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": false
|
||||
}
|
||||
],
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": [
|
||||
"runtime.js",
|
||||
"image/bookmark/*.svg",
|
||||
"image/logo.png",
|
||||
"image/info.svg",
|
||||
"image/repair.svg",
|
||||
"style/panel.css"
|
||||
],
|
||||
"matches": ["https://meet.google.com/*"]
|
||||
}
|
||||
],
|
||||
"permissions": ["storage"],
|
||||
"host_permissions": ["https://meet.google.com/*"]
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
addRoot(),addMiniPanel(),addRemindLogin();const checkOngoingMeeting=setInterval(tryTo(addCaptionPanel,"adding button"),1e3);let notificationsTimeout;const notificationsTimeoutDuration=3e3;let checkCaptionStatusInterval;function saveLog(e){const t={message:e,time:(new Date).toISOString()};chrome.storage.local.get("logs",(function(e){var n=structuredClone(e.logs);void 0===n?n=[t]:n.push(t),chrome.storage.local.set({logs:n})}))}syncSettings(),window.addEventListener("click",(e=>{const t=document.getElementById("laxis-downloadMenu");e.target===t&&(t.style.display="none")})),chrome.runtime.onMessage.addListener((function(e,t,n){if(t.id===extensionId)if(e.token){n("token received by content script");const t={method:"GET",headers:{Authorization:`Bearer ${e.token}`}},o=()=>window.fetch(`${domainUrl}/api/v1/users/info`,t),i=()=>window.fetch(`${domainUrl}/api/v2/templates?quick-note=true`,t),s=e=>window.fetch(`${domainUrl}/api/v2/templates/${e}/topics`,t);Promise.all([o(),i()]).then((t=>{if(200===t[0].status&&200===t[1].status){loginStatus=1;const e=document.getElementById("login-prompt");e&&(e.style.display="none");const n=document.getElementById("laxis-remindLogin");n&&(n.style.display="none"),t[0].json().then((e=>{appUser=`${e.firstName} ${e.lastName}`,chrome.runtime.sendMessage({type:"username",data:`${e.firstName} ${e.lastName}`})})),t[1].json().then((e=>{let t=bookmarkList;s(e.items[0].id).then((e=>e.json().then((e=>{e.items.forEach((e=>{let n=bookmarkList.findIndex((t=>t.code===e.color));if(-1!==n){if(t[n].name=e.name,document.getElementById(`laxis-highlight-${e.color}-title`)){document.getElementById(`laxis-highlight-${e.color}-title`).innerHTML=e.name}let o=document.getElementById(`laxis-highlight-${e.color}`),i=document.getElementById(`laxis-highlight-${e.color}-mini`);o&&(o.title=`Highlight as ${e.name}`),i&&(i.title=`Highlight as ${e.name}`)}}))})))).catch((e=>console.warn(e))),bookmarkList=t}))}else 401===t[0].status||401===t[1].status?(401===t[0].status?t[0].json().then((t=>{saveLog(`chrome.runtime.onMessage.addListener 401 fail ${t.message} ${e.token}`)})):t[1].json().then((t=>{saveLog(`chrome.runtime.onMessage.addListener 401 fail ${t.message} ${e.token}`)})),reDisplayPrompt(),reDisplayRemindLogin()):t[0].json().then((e=>{window.alert(e.message)}))})).catch((e=>{window.alert(e)}))}else saveLog(`chrome.runtime.onMessage.addListener token empty fail ${e.token}`),reDisplayRemindLogin()}));
|
||||
@@ -0,0 +1,269 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.panelBase {
|
||||
position: absolute;
|
||||
z-index: 998;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.col-12 {
|
||||
padding: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*toggle*/
|
||||
/*!*caption hover*!*/
|
||||
/*.highlight-caption:hover {*/
|
||||
/* background-color: #696969;*/
|
||||
/*}*/
|
||||
|
||||
/*tooltip*/
|
||||
.popup {
|
||||
position: relative;
|
||||
}
|
||||
.popup .popupText {
|
||||
position: absolute;
|
||||
visibility: hidden;
|
||||
width: 90%;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 7px 0;
|
||||
z-index: 1;
|
||||
bottom: calc(100% + 7px);
|
||||
left: 5%;
|
||||
font-size: 13px;
|
||||
color: #292c35;
|
||||
background-color: #818388;
|
||||
font-weight: bold;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.popup .show {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip:hover {
|
||||
background-color: #454953;
|
||||
border-radius: 12px;
|
||||
border: solid 1px #c7c7c7;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext {
|
||||
visibility: hidden;
|
||||
width: 100px;
|
||||
background-color: #454953;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
bottom: 100%;
|
||||
left: 98%;
|
||||
margin-left: -100px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.tooltip .tooltiptext::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #000000 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.settings-tooltip .tooltiplargetext {
|
||||
visibility: hidden;
|
||||
min-width: 200px;
|
||||
background-color: #454953;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #c7c7c7;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
bottom: 100%;
|
||||
left: 98%;
|
||||
margin-left: -100px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
padding: 2px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settings-tooltip .tooltiplargetext::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #000000 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.settings-tooltip:hover .tooltiplargetext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: absolute; /* Stay in place */
|
||||
z-index: 1500; /* Sit on top */
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 300px; /* Full width */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
}
|
||||
|
||||
/* Modal Content/Box */
|
||||
.modal-content {
|
||||
background-color: #454953;
|
||||
color: #ffffff;
|
||||
border: solid 1px #ffffff;
|
||||
margin-top: 98px;
|
||||
margin-left: 17px;
|
||||
width: 266px;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.modal-header {
|
||||
padding: 20px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-top: 24px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
}
|
||||
.modal-footer {
|
||||
border-top: 1px solid #e0e0e0;
|
||||
margin-top: 48px;
|
||||
padding-top: 12px;
|
||||
padding-bottom: 12px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.modal-button {
|
||||
width: 45%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.miniButtonContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: 50%;
|
||||
margin-left: 10px;
|
||||
margin-top: 12px;
|
||||
padding: 9px 0 9px 0;
|
||||
background-color: #3e4149;
|
||||
cursor: pointer;
|
||||
border: 1px solid #3e4149;
|
||||
}
|
||||
|
||||
.miniButtonContainer:hover {
|
||||
border: 1px solid #2196f3;
|
||||
}
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #292c35;
|
||||
padding: 7px;
|
||||
background-color: #292c35;
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.imageContainer:hover {
|
||||
border: 1px solid #2196f3;
|
||||
}
|
||||
.tutorialContainer {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
padding: 8px;
|
||||
margin: 12px;
|
||||
border-radius: 50%;
|
||||
background-color: #454953;
|
||||
}
|
||||
|
||||
.flagContainer {
|
||||
border-radius: 50%;
|
||||
border: 1px solid #9e9e9e;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.flagContainer:hover {
|
||||
border: 1px solid #2196f3;
|
||||
}
|
||||
|
||||
.flagContainerMini {
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
margin: 6px 0px;
|
||||
background-color: #292c35;
|
||||
}
|
||||
.flagContainerMini:hover {
|
||||
border: 1px solid #2196f3;
|
||||
}
|
||||
|
||||
.loading {
|
||||
border: 1px solid rgb(0, 38, 255);
|
||||
}
|
||||
.finishing {
|
||||
border: 1px solid #2196f3;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
const STYLE=document.createElement("style"),promise=new Promise((e=>{fetch(chrome.runtime.getURL("style/panel.css")).then((e=>e.text())).then((t=>{e(t)}))}));promise.then((e=>{STYLE.innerText=e.toString(),document.body.append(STYLE)}));
|
||||