[mirotalk] - refactor: extract renderRoomTemplate into standalone roomTemplate.js
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# ====================================================
|
# ====================================================
|
||||||
# MiroTalk P2P v.1.8.30 - Environment Configuration
|
# MiroTalk P2P v.1.8.31 - Environment Configuration
|
||||||
# ====================================================
|
# ====================================================
|
||||||
|
|
||||||
# App environment
|
# App environment
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ==============================================
|
* ==============================================
|
||||||
* MiroTalk P2P v.1.8.30 - Configuration File
|
* MiroTalk P2P v.1.8.31 - Configuration File
|
||||||
* ==============================================
|
* ==============================================
|
||||||
*
|
*
|
||||||
* This file is the central configuration source.
|
* This file is the central configuration source.
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ dependencies: {
|
|||||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.8.30
|
* @version 1.8.31
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalk",
|
"name": "mirotalk",
|
||||||
"version": "1.8.30",
|
"version": "1.8.31",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mirotalk",
|
"name": "mirotalk",
|
||||||
"version": "1.8.30",
|
"version": "1.8.31",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mattermost/client": "11.6.0",
|
"@mattermost/client": "11.6.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mirotalk",
|
"name": "mirotalk",
|
||||||
"version": "1.8.30",
|
"version": "1.8.31",
|
||||||
"description": "A free WebRTC browser-based video call",
|
"description": "A free WebRTC browser-based video call",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+1
-1
@@ -109,7 +109,7 @@ let brand = {
|
|||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
imageUrl: '../images/mirotalk-logo.gif',
|
imageUrl: '../images/mirotalk-logo.gif',
|
||||||
title: 'WebRTC P2P v1.8.30',
|
title: 'WebRTC P2P v1.8.31',
|
||||||
html: `
|
html: `
|
||||||
<button
|
<button
|
||||||
id="support-button"
|
id="support-button"
|
||||||
|
|||||||
+3
-48
@@ -15,7 +15,7 @@
|
|||||||
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
* @license For commercial use or closed source, contact us at license.mirotalk@gmail.com or purchase directly from CodeCanyon
|
||||||
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-p2p-webrtc-realtime-video-conferences/38376661
|
||||||
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com
|
||||||
* @version 1.8.30
|
* @version 1.8.31
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -15752,7 +15752,7 @@ function showAbout() {
|
|||||||
Swal.fire({
|
Swal.fire({
|
||||||
background: swBg,
|
background: swBg,
|
||||||
position: 'center',
|
position: 'center',
|
||||||
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.30',
|
title: brand.about?.title && brand.about.title.trim() !== '' ? brand.about.title : 'WebRTC P2P v1.8.31',
|
||||||
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
|
imageUrl: brand.about?.imageUrl && brand.about.imageUrl.trim() !== '' ? brand.about.imageUrl : images.about,
|
||||||
customClass: { image: 'img-about' },
|
customClass: { image: 'img-about' },
|
||||||
html: renderRoomTemplate('tpl-about-modal', {
|
html: renderRoomTemplate('tpl-about-modal', {
|
||||||
@@ -16883,49 +16883,4 @@ function sleep(ms) {
|
|||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// renderRoomTemplate is defined in roomTemplate.js
|
||||||
* Render HTML template with provided data
|
|
||||||
* @param {string} templateId - ID of the <template> element
|
|
||||||
* @param {object} data - Data to populate the template
|
|
||||||
* @param {object} data.text - Key-value pairs for text content (data-template-text)
|
|
||||||
* @param {object} data.html - Key-value pairs for HTML content (data-template-html
|
|
||||||
* @param {object} data.attrs - Key-value pairs for attributes (data-template-attr-*)
|
|
||||||
* @returns {string} Rendered HTML string
|
|
||||||
*/
|
|
||||||
function renderRoomTemplate(templateId, { text = {}, html = {}, attrs = {} } = {}) {
|
|
||||||
const template = document.getElementById(templateId);
|
|
||||||
if (!template || !template.content) return '';
|
|
||||||
|
|
||||||
const wrapper = document.createElement('div');
|
|
||||||
wrapper.appendChild(template.content.cloneNode(true));
|
|
||||||
|
|
||||||
wrapper.querySelectorAll('*').forEach((element) => {
|
|
||||||
element.getAttributeNames().forEach((name) => {
|
|
||||||
if (!name.startsWith('data-template-attr-')) return;
|
|
||||||
|
|
||||||
const attrName = name.replace('data-template-attr-', '');
|
|
||||||
const key = element.getAttribute(name);
|
|
||||||
const value = attrs[key];
|
|
||||||
|
|
||||||
if (value === undefined || value === null) {
|
|
||||||
element.removeAttribute(attrName);
|
|
||||||
} else {
|
|
||||||
element.setAttribute(attrName, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.removeAttribute(name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.querySelectorAll('[data-template-text]').forEach((element) => {
|
|
||||||
const key = element.getAttribute('data-template-text');
|
|
||||||
element.textContent = text[key] ?? '';
|
|
||||||
});
|
|
||||||
|
|
||||||
wrapper.querySelectorAll('[data-template-html]').forEach((element) => {
|
|
||||||
const key = element.getAttribute('data-template-html');
|
|
||||||
element.innerHTML = html[key] ?? '';
|
|
||||||
});
|
|
||||||
|
|
||||||
return wrapper.innerHTML.trim();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render HTML template with provided data
|
||||||
|
* @param {string} templateId - ID of the <template> element
|
||||||
|
* @param {object} data - Data to populate the template
|
||||||
|
* @param {object} data.text - Key-value pairs for text content (data-template-text)
|
||||||
|
* @param {object} data.html - Key-value pairs for HTML content (data-template-html
|
||||||
|
* @param {object} data.attrs - Key-value pairs for attributes (data-template-attr-*)
|
||||||
|
* @returns {string} Rendered HTML string
|
||||||
|
*/
|
||||||
|
function renderRoomTemplate(templateId, { text = {}, html = {}, attrs = {} } = {}) {
|
||||||
|
const template = document.getElementById(templateId);
|
||||||
|
if (!template || !template.content) return '';
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.appendChild(template.content.cloneNode(true));
|
||||||
|
|
||||||
|
wrapper.querySelectorAll('*').forEach((element) => {
|
||||||
|
element.getAttributeNames().forEach((name) => {
|
||||||
|
if (!name.startsWith('data-template-attr-')) return;
|
||||||
|
|
||||||
|
const attrName = name.replace('data-template-attr-', '');
|
||||||
|
const key = element.getAttribute(name);
|
||||||
|
const value = attrs[key];
|
||||||
|
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
element.removeAttribute(attrName);
|
||||||
|
} else {
|
||||||
|
element.setAttribute(attrName, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.removeAttribute(name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.querySelectorAll('[data-template-text]').forEach((element) => {
|
||||||
|
const key = element.getAttribute('data-template-text');
|
||||||
|
element.textContent = text[key] ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.querySelectorAll('[data-template-html]').forEach((element) => {
|
||||||
|
const key = element.getAttribute('data-template-html');
|
||||||
|
element.innerHTML = html[key] ?? '';
|
||||||
|
});
|
||||||
|
|
||||||
|
return wrapper.innerHTML.trim();
|
||||||
|
}
|
||||||
@@ -1843,6 +1843,7 @@ Note</textarea
|
|||||||
<script defer src="../js/fixWebmDuration.js"></script>
|
<script defer src="../js/fixWebmDuration.js"></script>
|
||||||
<script defer src="../js/screenReader.js"></script>
|
<script defer src="../js/screenReader.js"></script>
|
||||||
<script defer src="../js/wakeLock.js"></script>
|
<script defer src="../js/wakeLock.js"></script>
|
||||||
|
<script defer src="../js/roomTemplate.js"></script>
|
||||||
<script defer src="../js/client.js"></script>
|
<script defer src="../js/client.js"></script>
|
||||||
<script defer src="../js/networkStats.js"></script>
|
<script defer src="../js/networkStats.js"></script>
|
||||||
<script defer src="../js/speechRecognition.js"></script>
|
<script defer src="../js/speechRecognition.js"></script>
|
||||||
|
|||||||
@@ -7,66 +7,7 @@ const path = require('path');
|
|||||||
const vm = require('vm');
|
const vm = require('vm');
|
||||||
const { JSDOM } = require('jsdom');
|
const { JSDOM } = require('jsdom');
|
||||||
|
|
||||||
function extractNamedFunction(source, functionName) {
|
const templateSource = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'roomTemplate.js'), 'utf8');
|
||||||
const signature = `function ${functionName}`;
|
|
||||||
const start = source.indexOf(signature);
|
|
||||||
|
|
||||||
if (start === -1) {
|
|
||||||
throw new Error(`Unable to find ${functionName} in source file`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramsStart = source.indexOf('(', start);
|
|
||||||
let paramsDepth = 0;
|
|
||||||
let bodyStart = -1;
|
|
||||||
|
|
||||||
for (let index = paramsStart; index < source.length; index++) {
|
|
||||||
const char = source[index];
|
|
||||||
|
|
||||||
if (char === '(') paramsDepth++;
|
|
||||||
if (char === ')') paramsDepth--;
|
|
||||||
|
|
||||||
if (paramsDepth === 0) {
|
|
||||||
bodyStart = source.indexOf('{', index);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bodyStart === -1) {
|
|
||||||
throw new Error(`Unable to find ${functionName} body in source file`);
|
|
||||||
}
|
|
||||||
|
|
||||||
let depth = 0;
|
|
||||||
|
|
||||||
for (let index = bodyStart; index < source.length; index++) {
|
|
||||||
const char = source[index];
|
|
||||||
|
|
||||||
if (char === '{') depth++;
|
|
||||||
if (char === '}') depth--;
|
|
||||||
|
|
||||||
if (depth === 0) {
|
|
||||||
return source.slice(start, index + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unable to parse ${functionName} from source file`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadNamedFunctionSource(functionName, relativePaths) {
|
|
||||||
for (const relativePath of relativePaths) {
|
|
||||||
const sourcePath = path.join(__dirname, '..', ...relativePath.split('/'));
|
|
||||||
const source = fs.readFileSync(sourcePath, 'utf8');
|
|
||||||
|
|
||||||
try {
|
|
||||||
return extractNamedFunction(source, functionName);
|
|
||||||
} catch (error) {
|
|
||||||
if (!error.message.includes(`Unable to find ${functionName}`)) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unable to find ${functionName} in source file`);
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('test-RoomTemplates', () => {
|
describe('test-RoomTemplates', () => {
|
||||||
let renderRoomTemplate;
|
let renderRoomTemplate;
|
||||||
@@ -76,9 +17,9 @@ describe('test-RoomTemplates', () => {
|
|||||||
const dom = new JSDOM('<!doctype html><html><body></body></html>');
|
const dom = new JSDOM('<!doctype html><html><body></body></html>');
|
||||||
document = dom.window.document;
|
document = dom.window.document;
|
||||||
|
|
||||||
const functionSource = loadNamedFunctionSource('renderRoomTemplate', ['public/js/client.js']);
|
const context = vm.createContext({ document, module: {}, exports: {} });
|
||||||
|
vm.runInContext(templateSource, context);
|
||||||
renderRoomTemplate = vm.runInNewContext(`(${functionSource})`, { document });
|
renderRoomTemplate = context.renderRoomTemplate;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('preserves empty string attributes used by placeholder options', () => {
|
it('preserves empty string attributes used by placeholder options', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user