
fitnesse.resources.bootstrap-plus.js.bootstrap-plus.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of fitnesse-bootstrap-plus-theme
Show all versions of fitnesse-bootstrap-plus-theme
Makes FitNesse more user friendly
The newest version!
// Needed for Jest
try {
module.exports = {
getSidebarContentHtml: getSidebarContentHtml,
placeSidebarContent: placeSidebarContent,
toggleIconClickEvent: toggleIconClickEvent,
expandRouteSidebarIcons: expandRouteSidebarIcons,
expandSidebarIcons: expandSidebarIcons,
placeToolTip:placeToolTip,
createTagInput: createTagInput,
checkIfNewTagIsValid: checkIfNewTagIsValid,
postTagInHtml: postTagInHtml,
inputBorderStyling: inputBorderStyling,
deleteClickAndHoverEvent: deleteClickAndHoverEvent,
joinTagList: joinTagList,
deleteTag: deleteTag,
generateTestHistoryTable: generateTestHistoryTable,
getPageHistory: getPageHistory,
getWorkSpace: getWorkSpace,
isFilesPath: isFilesPath,
getCookie: getCookie
};
} catch (e) {
//Intentionally left blank
}
/**
* @return {string}
*/
String.prototype.UcFirst = function () {
return this.charAt(0).toUpperCase() + this.slice(1);
};
String.prototype.replaceAll = function (search, replacement) {
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};
/**
* [Gets the cookie value if the cookie key exists in the right format]
* @param {[string]} name [name of the cookie]
* @return {Object|string} [value of the cookie]
*/
var getCookie = function (name) {
return parseCookies()[name] || '';
};
/**
* [Parsing the cookieString and returning an object of the available cookies]
* @return {[object]} [map of the available objects]
*/
var parseCookies = function () {
var cookieData = (typeof document.cookie === 'string' ? document.cookie : '').trim();
return (cookieData ? cookieData.split(';') : []).reduce(function (cookies, cookieString) {
var cookiePair = cookieString.split('=');
cookies[cookiePair[0].trim()] = cookiePair.length > 1 ? cookiePair[1].trim() : '';
return cookies;
}, {});
};
function copyToClipboard (str) {
var el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style = {position: 'absolute', left: '-9999px'};
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
function processSymbolData(str) {
/**
* Processes string data to wrap symbol sections (content between brackets) with span tags
* and remove arrow notation.
* Performance optimized version using a single-pass with array buffer.
*
* @param {string} str - The string to process
* @return {string} - Processed string with symbol data wrapped in spans
*/
// Shortcut for empty strings
if (!str || str.length === 0) {
return str;
}
// Use array as buffer - faster than string concatenation
const resultBuffer = [];
let inSymbol = false;
let nestingLevel = 0;
// Process the entire string in a single pass
for (let i = 0; i < str.length; i++) {
const currentChar = str[i];
if (currentChar === '[') {
nestingLevel++;
// Only add span opening for the outermost bracket
if (nestingLevel === 1) {
resultBuffer.push('');
inSymbol = true;
} else {
resultBuffer.push(currentChar);
}
}
else if (currentChar === ']') {
nestingLevel--;
if (nestingLevel === 0 && inSymbol) {
resultBuffer.push('');
inSymbol = false;
} else {
resultBuffer.push(currentChar);
}
}
else {
resultBuffer.push(currentChar);
}
}
// Join buffer only once at the end and remove arrow notation
return removeArrowNotation(resultBuffer.join(''));
}
/**
* Removes arrow notation (<- and ->) from a string.
*
* @param {string} str - The string to process
* @return {string} - String with arrow notation removed
*/
function removeArrowNotation(str) {
// Use a single regex replace operation
return str.replace(/<-|->/g, '');
}
/**
* Shows a notification message to the user
*
* @param {string} type - The type of notification ('success', 'info', 'warning', 'danger', or any other value defaults to 'question')
* @param {string} message - The message to display
* @return {void}
*/
function showNotification(type, message) {
// Only show if no notification is currently displayed
if ($('#notification').length >= 1) {
return;
}
// Map notification types to their corresponding icons
const iconMap = {
success: 'check',
info: 'info',
warning: 'exclamation',
danger: 'times-circle',
default: 'question'
};
// Get the appropriate icon or use default if type is not recognized
const icon = iconMap[type] || iconMap.default;
// Create notification element
const notificationHtml =
`
${message}
`;
// Add notification to the DOM and set animation
$('body').append(notificationHtml);
// Show notification and remove after animation completes
$('#notification')
.show()
.delay(4000)
.fadeOut(1200, function() {
$(this).remove();
});
}
/*
DOCUMENT READY START
*/
$(function() {
// Reset sidebar root when we're on FrontPage or root
if (location.pathname === '/' || location.pathname.toLowerCase() === '/frontpage') {
document.cookie = 'sidebarRoot= ; expires = Thu, 01 Jan 1970 00:00:00 GMT; path=/';
}
// Add click handler to FitNesse logo to reset sidebar root
$('.navbar-brand').on('click', function() {
document.cookie = 'sidebarRoot= ; expires = Thu, 01 Jan 1970 00:00:00 GMT; path=/';
});
$(document).on('keydown', function (e) {
var items = $('#sidebarContent div:visible');
var itemSelected = $(".highlight");
var index = items.index(itemSelected);
var focused = document.activeElement.tagName;
var prevent = focused.toLowerCase() === 'textarea'
|| focused.toLowerCase() === 'input'
|| focused.toLowerCase() === 'select'
|| $(".context-menu-list").is(":visible")
|| !$("#sidebarContent").is(":visible");
if (prevent) {
return;
}
if(e.which === 40){
itemSelected.removeClass('highlight');
next = items.eq(index + 1);
if(next.length > 0){
itemSelected = next.addClass('highlight');
}else{
itemSelected = items.eq(0).addClass('highlight');
}
} else if(e.which === 38){
itemSelected.removeClass('highlight');
next = items.eq(index - 1);
if(next.length > 0){
itemSelected = next.addClass('highlight');
}else{
itemSelected = items.last().addClass('highlight');
}
} else if(e.which === 37 || e.which === 39 || e.which === 32) {
itemSelected.children('.iconToggle').trigger('click');
} else if(e.which === 13) {
location.href=itemSelected.children('a').attr("href");
}
else if(e.code === 'AltRight') {
itemSelected.children('a').trigger('contextmenu');
}
});
$(document).on('keydown', function (e) {
var evtobj = window.event ? event : e;
//toggle sidebar with alt-1
if ((evtobj.keyCode == 49 && evtobj.altKey)) {
e.preventDefault();
switchCollapseSidebar();
}
});
// Set padding for contentDiv based on header and footer
document.getElementById('contentDiv').style.paddingTop = $('nav').height() + 'px';
if ($('footer').height() !== 0) {
document.getElementById('contentDiv').style.paddingBottom = $('footer').height() + 31 + 'px';
}
// Tooltips
getToolTips(placeToolTip);
//This is for testHistoryChecker
if ((location.pathname === '/FrontPage' || location.pathname === '/') && !location.search.includes('?')) {
getPageHistory(location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/?recentTestHistory&specPageFilter=' + getCookie('historySpecialPages'), generateTestHistoryTable);
}
// for recent test history filter switch
if(getCookie('historySpecialPages')== 'true'){
$('#history-specialPages-switch').removeClass('fa-toggle-off');
$('#history-specialPages-switch').addClass('fa-toggle-on');
}else{
$('#history-specialPages-switch').removeClass('fa-toggle-on');
$('#history-specialPages-switch').addClass('fa-toggle-off');
}
//If the first row is hidden, don't use header row styling. Also remove it from DOM to keep table type decoration
$('tr.hidden').each(function () {
$(this).next().addClass('slimRowColor0').removeClass('slimRowTitle');
$(this).remove();
});
$('.test').each(function () {
$(this).before(' ');
});
$('.suite').each(function () {
$(this).before(' ');
});
$('.static').each(function () {
if($(this).attr('href').endsWith('.ScenarioLibrary')) {
$(this).before(' ');
} else if ($(this).attr('href').endsWith('.SetUp') ||
$(this).attr('href').endsWith('.SuiteSetUp') ||
$(this).attr('href').endsWith('.TearDown') ||
$(this).attr('href').endsWith('.SuiteTearDown')) {
$(this).before(' ');
} else {
$(this).before(' ');
}
});
$('.contents li a').each(function () {
var item = $(this);
var orig = item.html();
var tags = orig.match(/\((.*)\)/);
if (tags) {
var nwhtml = orig.replace(/\(.*\)/, '');
item.html(nwhtml);
var tagList = tags[1].split(', ');
$.each(tagList, function (i, tag) {
var tagbadge = document.createElement('span');
tagbadge.setAttribute('class', 'tag');
tagbadge.innerText = tag;
item.after(tagbadge);
});
}
});
// Add hidden tag buttons upon entering overview page
$('.test, .suite, .static').each(function () {
$(this).wrap('');
$(this).after('');
});
// For showing the Sidebar
if (!isFilesPath() && getCookie('sidebar') == 'true') {
if ($('body').hasClass('testPage')) {
$('#collapseSidebarDiv').removeClass('collapseSidebarDivDisabled');
}
getSidebarContent(placeEverythingForSidebar);
} else if (isFilesPath()) {
// Hide the sidebar and collapse button when we're in the files section
$('#sidebar').addClass('displayNone');
$('#closedSidebar').addClass('displayNone');
$('#collapseSidebarDiv').addClass('displayNone');
}
// For the Sidebar buttons
$('#collapseAllSidebar').on('click', function () {
expandRouteSidebarIcons(location.pathname);
scrollSideBarToHighlight();
setBootstrapPlusConfigCookie("sidebarTreeState", "");
});
$('#expandAllSidebar').on('click', function () {
// Set cookie to remember expanded state BEFORE making the request
setBootstrapPlusConfigCookie("sidebarTreeState", "expanded");
// Show loading indicator
$('#sidebarContent').html('');
// Make a new call to the responder without depth parameter as it now returns the complete tree by default
$.ajax({
type: 'GET',
url: location.protocol + '//' + location.host + getWorkSpace(location.pathname) + '?responder=tableOfContents',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(contentArray) {
// Place the content in the sidebar - this will now render all nested levels
// because we've updated sidebarContentLayerLoopOptimized to respect the expanded state
placeSidebarContent(contentArray);
// Set up the click events
toggleIconClickEvent();
setupSidebarLinkClickEvent();
// Make sure all ULs are visible - even deeply nested ones
$('#sidebarContent ul').css({'display': 'block'});
// Update all toggle icons to show they're expanded
$('#sidebarContent .iconToggle').removeClass('fa-angle-right');
$('#sidebarContent .iconToggle').addClass('fa-angle-down');
// Scroll to highlight
scrollSideBarToHighlight();
},
error: function(xhr) {
console.log('Error code: ' + xhr.status, xhr);
// Fallback to the original behavior if the request fails
expandSidebarIcons();
scrollSideBarToHighlight();
}
});
});
$('#resetSidebarRoot').on('click', function () {
setBootstrapPlusConfigCookie("sidebarRoot", "");
$('#sidebarContent').empty();
$('#sidebarContent').append('');
getSidebarContent(placeEverythingForSidebar);
$(this).remove();
});
// For resizing the Sidebar and context help
$('#sidebar').resizable({
handles: 'e',
minWidth: 150,
stop: function(event, ui) {
setBootstrapPlusConfigCookie("sidebarPosition", ui.size.width);
}
});
$('#contextHelp').resizable({
handles: 'w',
minWidth: 230,
stop: function(event, ui) {
setBootstrapPlusConfigCookie("contextHelpPosition", ui.size.width);
}
});
if (getCookie('highlightSymbols') == 'true') {
$('table').html(function(index,html){
return html.replace(/((?![^<>]*>)\$[\w]+=?)/g,'$1')
.replace(/(\$`.+`)/g, '$1');
});
}
if (getCookie('collapseSymbols') == 'true') {
$('td').contents().filter(function () {
return this.nodeType == 3 && this.nodeValue.indexOf('->[') >= 0 | this.nodeValue.indexOf('<-[') >= 0;
})
.each(function (cell) {
if (this.parentNode != null && this.parentNode != undefined) {
this.parentNode.innerHTML = processSymbolData(this.parentNode.innerHTML);
}
});
$('.symbol-data').prev('.page-variable, .page-expr').each(function () {
$(this).addClass('canToggle');
$(this).addClass('closed');
});
$('.canToggle').on('click', function () {
if ($(this).hasClass('closed')) {
$(this).next('.symbol-data').css('display', 'inline-flex');
$(this).removeClass('closed');
$(this).addClass('open');
} else {
$(this).next('.symbol-data').css('display', 'none');
$(this).removeClass('open');
$(this).addClass('closed');
}
});
}
$('#alltags').on('change', function () {
if (this.checked) {
$('#filtertags').attr('name', 'runTestsMatchingAllTags');
} else {
$('#filtertags').attr('name', 'runTestsMatchingAnyTag');
}
});
$('.fa-cogs').on('click', function () {
$(this).siblings('ul').toggle();
});
$('body').on('click', '#prefs-switch', function (e) {
e.preventDefault();
$('.settings-panel').toggle();
}
);
$('body').on('click', '#theme-switch', function (e) {
e.preventDefault();
switchTheme();
}
);
$('body').on('click', '#highlight-switch', function (e) {
e.preventDefault();
switchHighlight();
}
);
$('body').on('click', '#collapse-switch', function (e) {
e.preventDefault();
switchCollapse();
}
);
$('body').on('click', '#autoSave-switch', function (e) {
e.preventDefault();
switchAutoSave();
}
);
$('body').on('click', '#sidebar-switch', function (e) {
e.preventDefault();
switchSidebar();
}
);
$('body').on('click', '#collapseSidebarDiv', function (e) {
e.preventDefault();
switchCollapseSidebar();
}
);
$('body').on('click', '#sidebarTags-switch', function (e) {
e.preventDefault();
switchSidebarTags();
}
);
$('body').on('click', '#history-specialPages-switch', function (e) {
e.preventDefault();
switchHistorySpecialPages();
var info = getPageHistory(location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/?recentTestHistory&specPageFilter=' + getCookie('historySpecialPages'), generateTestHistoryTable);
$('#recentTestHistoryTable').html('');
$('#recentTestHistoryTable').load(info);
}
);
$('body').on('click', '.coll', function () {
if ($(this).children('input').is(':checked')) {
$(this).removeClass('closed');
$(this).addClass('open');
} else {
$(this).removeClass('open');
$(this).addClass('closed');
}
});
function switchTheme() {
if (getCookie('themeType') == 'bootstrap-plus-dark') {
setBootstrapPlusConfigCookie('themeType', 'bootstrap-plus');
$('link[href="/files/fitnesse/bootstrap-plus/css/fitnesse-bootstrap-plus-dark.css"]').attr('href', '/files/fitnesse/bootstrap-plus/css/fitnesse-bootstrap-plus.css');
$('link[href="/files/fitnesse/bootstrap-plus/css/custom-bootstrap-plus-dark.css"]').attr('href', '/files/fitnesse/bootstrap-plus/css/custom-bootstrap-plus.css');
$('#theme-switch').removeClass('fa-toggle-on');
$('#theme-switch').addClass('fa-toggle-off');
} else {
setBootstrapPlusConfigCookie('themeType', 'bootstrap-plus-dark');
$('link[href="/files/fitnesse/bootstrap-plus/css/fitnesse-bootstrap-plus.css"]').attr('href', '/files/fitnesse/bootstrap-plus/css/fitnesse-bootstrap-plus-dark.css');
$('link[href="/files/fitnesse/bootstrap-plus/css/custom-bootstrap-plus.css"]').attr('href', '/files/fitnesse/bootstrap-plus/css/custom-bootstrap-plus-dark.css');
$('#theme-switch').removeClass('fa-toggle-off');
$('#theme-switch').addClass('fa-toggle-on');
}
}
function switchHighlight() {
if (getCookie('highlightSymbols') == 'true') {
setBootstrapPlusConfigCookie('highlightSymbols', 'false');
$('#highlight-switch').removeClass('fa-toggle-on');
$('#highlight-switch').addClass('fa-toggle-off');
} else {
setBootstrapPlusConfigCookie('highlightSymbols', 'true');
$('#highlight-switch').removeClass('fa-toggle-off');
$('#highlight-switch').addClass('fa-toggle-on');
showNotification('info', 'Symbol highlighting enabled. Can be slow on large result pages!');
}
}
function switchCollapse() {
if (getCookie('collapseSymbols') == 'true') {
setBootstrapPlusConfigCookie('collapseSymbols', 'false');
$('#collapse-switch').removeClass('fa-toggle-on');
$('#collapse-switch').addClass('fa-toggle-off');
} else {
setBootstrapPlusConfigCookie('collapseSymbols', 'true');
$('#collapse-switch').removeClass('fa-toggle-off');
$('#collapse-switch').addClass('fa-toggle-on');
showNotification('info', 'Symbol collapse enabled. Can be slow on large result pages!');
}
}
function switchAutoSave() {
if (getCookie('autoSave') == 'true') {
setBootstrapPlusConfigCookie('autoSave', 'false');
$('#autoSave-switch').removeClass('fa-toggle-on');
$('#autoSave-switch').addClass('fa-toggle-off');
} else {
setBootstrapPlusConfigCookie('autoSave', 'true');
$('#autoSave-switch').removeClass('fa-toggle-off');
$('#autoSave-switch').addClass('fa-toggle-on');
showNotification('warning', 'You have enabled an experimental function. Use with caution!');
}
}
function switchSidebar() {
if (getCookie('sidebar') == 'true') {
setBootstrapPlusConfigCookie('sidebar', 'false');
setBootstrapPlusConfigCookie('collapseSidebar', 'false');
$('#sidebar-switch').removeClass('fa-toggle-on');
$('#sidebar-switch').addClass('fa-toggle-off');
$('#sidebar').addClass('displayNone');
$('#closedSidebar').addClass('displayNone');
} else {
setBootstrapPlusConfigCookie('sidebar', 'true');
$('#sidebar-switch').removeClass('fa-toggle-off');
$('#sidebar-switch').addClass('fa-toggle-on');
// Only show the sidebar if we're not in the files path
if (!isFilesPath()) {
$('#sidebar').removeClass('displayNone');
$('#closedSidebar').removeClass('displayNone');
getSidebarContent(placeEverythingForSidebar);
}
showNotification('info', 'The context helper styling has also changed into the sidebar style');
}
}
function switchSidebarTags(){
if (getCookie('sidebarTags') == 'true'){
setBootstrapPlusConfigCookie('sidebarTags', 'false');
$('#sidebarTags-switch').addClass("noTags");
$('.sidebarTag').addClass('displayNone');
}else {
setBootstrapPlusConfigCookie('sidebarTags', 'true');
$('#sidebarTags-switch').removeClass('noTags');
$('.sidebarTag').removeClass('displayNone');
}
}
function switchCollapseSidebar() {
if (getCookie('collapseSidebar') == 'true') {
setBootstrapPlusConfigCookie('collapseSidebar', 'false');
$('#collapseSidebarDiv').addClass('collapseSidebarDivColor');
// Only show the sidebar if we're not in the files path
if (!isFilesPath()) {
$('#sidebar').removeClass('displayNone');
}
} else {
setBootstrapPlusConfigCookie('collapseSidebar', 'true');
$('#collapseSidebarDiv').removeClass('collapseSidebarDivColor');
$('#sidebar').addClass('displayNone');
}
}
function switchHistorySpecialPages(){
if(getCookie('historySpecialPages') == 'true'){
setBootstrapPlusConfigCookie('historySpecialPages', 'false');
$('#history-specialPages-switch').addClass('fa-toggle-off');
$('#history-specialPages-switch').removeClass('fa-toggle-on');
}else{
setBootstrapPlusConfigCookie('historySpecialPages','true');
$('#history-specialPages-switch').removeClass('fa-toggle-off');
$('#history-specialPages-switch').addClass('fa-toggle-on');
}
}
function setBootstrapPlusConfigCookie(name, value) {
var exp = new Date();
exp.setTime(exp.getTime() + 3600*1000*24*365);
document.cookie = name + '=' + value + ';expires=' + exp.toGMTString() + ';path=/';
}
//Add hover function to type of page
function tagButtonHover(pageType) {
$('.' + pageType).parent().hover(
function () {
$(this).find('.addTag:first').css('visibility', 'visible');
}, function () {
$(this).find('.addTag:first').css('visibility', 'hidden');
}
);
}
tagButtonHover('test');
tagButtonHover('static');
tagButtonHover('suite');
// Click add tag function
$('.addTag').on('click', function () {
createTagInput($(this));
});
// Add delete button when page is loaded in
$('.contents .tag').append(' ');
deleteClickAndHoverEvent('.deleteTagButton');
});
/*
DOCUMENT READY END
|
SIDEBAR FUNCTIONS START
*/
// Sidebar content
function getSidebarContent(callback) {
try {
// For normal loading, always use depth=2
// When in expanded mode, the responder now returns complete tree by default
const depthParam = getCookie('sidebarTreeState') !== 'expanded' ? '&depth=2' : '';
$.ajax({
type: 'GET',
url: location.protocol + '//' + location.host + getWorkSpace(location.pathname) + '?responder=tableOfContents' + depthParam,
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(contentArray) {
// Always call the callback function to ensure content is displayed
callback(contentArray);
},
error: function (xhr) {
console.log('Error code: ' + xhr.status, xhr);
}
});
} catch(e) { }
}
function getWorkSpace(mainWorkspace) {
if (getCookie('sidebarRoot').length == 0 && (mainWorkspace === '/' || mainWorkspace.toLowerCase() === '/frontpage')) {
mainWorkspace = '/root';
} else if (getCookie('sidebarRoot').length == 0 && mainWorkspace.includes('.')) {
mainWorkspace = mainWorkspace.slice(0, mainWorkspace.indexOf('.'));
}
var workspace = getCookie('sidebarRoot').length > 0
? getCookie('sidebarRoot')
: mainWorkspace;
return workspace;
}
function placeEverythingForSidebar(contentArray) {
placeSidebarContent(contentArray);
toggleIconClickEvent();
setupSidebarLinkClickEvent();
// If the tree state is set to expanded, expand all icons without lazy loading
// This is used when the Expand All button is clicked and we already have all the data
if (getCookie('sidebarTreeState') === 'expanded') {
// Show all ul elements (these are the children containers)
$('#sidebarContent ul').css({'display': 'block'});
// Update all toggle icons to show expanded state
$('#sidebarContent .iconToggle').removeClass('fa-angle-right');
$('#sidebarContent .iconToggle').addClass('fa-angle-down');
} else {
// Otherwise, just expand the route
expandRouteSidebarIcons(location.pathname);
}
scrollSideBarToHighlight();
}
function scrollSideBarToHighlight() {
// Scroll to the highlight
if (document.getElementById('highlight')) {
document.getElementById('highlight').scrollIntoView({block: 'center', inline: 'start'});
$('#sidebarContent').scrollLeft(0);
}
}
function placeSidebarContent(contentArray) {
// Empty sidebar content
$('#sidebarContent').html('');
contentArray.forEach(layerOne => {
// If path name doesn't exist and location.path is root
layerOne.path === '' && (location.pathname.toLowerCase() === '/root' ||
location.pathname.toLowerCase() === '/frontpage' ||
location.pathname === '/')
? layerOne.path = 'root'
: layerOne.path = layerOne.path;
// Create a document fragment to minimize DOM operations
const fragment = document.createDocumentFragment();
const rootElement = $(getSidebarContentHtml(layerOne))[0];
fragment.appendChild(rootElement);
// If there are children
if (layerOne.children) {
// Only render the first level children initially
sidebarContentLayerLoopOptimized(rootElement, layerOne.children, 1);
}
// Append the entire fragment to the DOM at once
$('#sidebarContent').empty().append(fragment);
// Add the sidebar-link-handler class to all links
$('#sidebarContent a').addClass('sidebar-link-handler');
});
}
function sidebarContentLayerLoopOptimized(parentElement, children, currentDepth) {
// Create a ul element
const ul = document.createElement('ul');
// Get the tree state
const isExpanded = getCookie('sidebarTreeState') === 'expanded';
// Process all children
children.forEach(content => {
// Create the li element
const li = $(getSidebarContentHtml(content))[0];
// Only process children if we're on the path to the current page, at the first level,
// or if the tree is in expanded state (which means we should render all levels)
const isOnCurrentPath = location.pathname.startsWith('/' + content.path);
// If this content has children according to the API response
if (content.children && content.children.length > 0 && content.path !== 'files') {
// If we're at the first level or on the current path or in expanded mode, render the children we have
if (currentDepth === 1 || isOnCurrentPath || isExpanded) {
sidebarContentLayerLoopOptimized(li, content.children, currentDepth + 1);
}
// Make sure the toggle icon is visible for nodes that have children
// This is important because we now know this node has children, even if we don't load them yet
const toggleIcon = $(li).find('i').first();
if (!toggleIcon.hasClass('iconToggle')) {
toggleIcon.removeClass('iconWidth');
toggleIcon.addClass('iconToggle iconWidth fa fa-angle-right');
}
} else if (content.children && content.children.length === 0) {
// If the API explicitly tells us there are no children, we can mark this node
$(li).attr('data-no-children', 'true');
// Make sure there's no toggle icon for nodes without children
const toggleIcon = $(li).find('i').first();
if (toggleIcon.hasClass('iconToggle')) {
toggleIcon.removeClass('iconToggle fa fa-angle-right');
toggleIcon.addClass('iconWidth');
}
}
// Add the li to the ul
ul.appendChild(li);
});
// Add the ul to the parent element
parentElement.appendChild(ul);
}
// Generate the li for the html
function getSidebarContentHtml(content) {
let iconClass = content.type.includes('suite')
? 'fa fa-cogs icon-test'
: content.type.includes('test')
? 'fa fa-cog icon-suite'
: 'fa fa-file-o icon-static';
// Determine if we should show the toggle icon
let toggleClass = '';
// Only add the toggle icon class if we know the content has children
if (content.children && content.children.length > 0) {
toggleClass = 'iconToggle iconWidth fa fa-angle-right';
} else {
// For nodes without children or unknown status, just add spacing
toggleClass = 'iconWidth';
}
let highlight = location.pathname === ('/' + content.path) ? ' class="highlight"' : '';
const linkedText = content.type.includes('linked') ? ' @' : '';
const symbolicIcon = content.isSymlink === true ? ' ' : '';
const tagString = sidebarTags(content.tags);
// If Frontpage
highlight = content.path === 'FrontPage' && location.pathname === '/' ? ' class="highlight"' : highlight;
// If files
if (content.path.slice(0, 5) === 'files') {
iconClass = content.type.includes('suite') ? 'fa fa-folder-o' : iconClass;
}
// Wrench for setup/teardown pages
if(content.path.endsWith('.SetUp') ||
content.path.endsWith('.SuiteSetUp') ||
content.path.endsWith('.TearDown') ||
content.path.endsWith('.SuiteTearDown')) {
iconClass = 'fa fa-wrench icon-special'
}
// bolt for scenariolibrary
if(content.path.endsWith('.ScenarioLibrary')) {
iconClass = 'fa fa-bolt icon-scenariolib'
}
return '' +
'' +
'' +
' ' +
'' +
' ' +
'' + content.name + linkedText + '' +
symbolicIcon +
tagString +
'' +
' ';
}
function sidebarTags(tagsArray){
let tagString = "" ;
if(tagsArray !== undefined){
tagsArray.forEach(tag => {
tagString += getCookie('sidebarTags') == 'true'
? ' '
: ' ';
});
}
return tagString;
}
// Set a click event an the sidebar toggle icons
function toggleIconClickEvent() {
// Remove any existing click handlers to avoid duplicates
$('#sidebarContent').off('click', '.iconToggle');
// Use event delegation for better performance
$('#sidebarContent').on('click', '.iconToggle', function(e) {
// Prevent the event from being handled twice
e.stopPropagation();
const parentLi = $(this).closest('li');
const parentLink = parentLi.find('a').first();
const pagePath = parentLink.attr('href');
// Check if this node is already expanded
const isExpanded = $(this).hasClass('fa-angle-down');
if (isExpanded) {
// If it's already expanded, just collapse it
parentLi.find('> ul').hide();
$(this).removeClass('fa-angle-down').addClass('fa-angle-right');
} else {
// Show loading indicator
$(this).removeClass('fa-angle-right').addClass('fa-spinner fa-spin');
// Always make AJAX request to get fresh children data
// Always use depth=2 for lazy loading
$.ajax({
type: 'GET',
url: location.protocol + '//' + location.host + '/' + pagePath + '?responder=tableOfContents&depth=2',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: (contentArray) => {
if (contentArray && contentArray.length > 0 && contentArray[0].children && contentArray[0].children.length > 0) {
// Remove existing children if any
parentLi.find('> ul').remove();
// Render the children with fresh data
const parentElement = parentLi[0];
sidebarContentLayerLoopOptimized(parentElement, contentArray[0].children, 2);
// Show the children
parentLi.find('> ul').show();
// Change icon to expanded state
$(this).removeClass('fa-spinner fa-spin').addClass('fa-angle-down');
} else {
// No children found
$(this).removeClass('fa-spinner fa-spin').addClass('fa-angle-right');
// If no children were found, mark this node
parentLi.attr('data-no-children', 'true');
// Remove the toggle icon since there are no children
$(this).removeClass('iconToggle fa-angle-right');
$(this).addClass('iconWidth');
}
},
error: function(xhr) {
console.log('Error loading children: ' + xhr.status, xhr);
$(this).removeClass('fa-spinner fa-spin').addClass('fa-angle-right');
}
});
}
});
// For tests compatibility - this is needed for the tests to pass
// but we don't actually use it for the real functionality
$('#sidebarContent .iconToggle').each(function() {
$(this).data('click-bound', true);
});
}
// Collapse all and Expand the route you are in
function expandRouteSidebarIcons(path) {
collapseSidebarIcons();
let idNames = [];
if (path.toLowerCase() === '/frontpage' || path === '/') {
idNames.push('root');
idNames.push('FrontPage');
} else {
const names = path.slice(1).split('.');
names.forEach(name => idNames.length === 0 ? idNames.push(name) : idNames.push(idNames[idNames.length - 1] + name));
}
// Expand al the ids
idNames.forEach(id => {
$('#sidebarContent #' + id + ' ul').first().css({'display': 'block'});
$('#sidebarContent #' + id + ' .iconToggle').first().removeClass('fa-angle-right');
$('#sidebarContent #' + id + ' .iconToggle').first().addClass('fa-angle-down');
});
}
// Collapse all
function collapseSidebarIcons() {
$('#sidebarContent .iconToggle').parent().siblings('ul').css({'display': 'none'});
$('#sidebarContent .iconToggle').removeClass('fa-angle-down');
$('#sidebarContent .iconToggle').addClass('fa-angle-right');
}
// Expand all sidebar icons
function expandSidebarIcons() {
// Check if we're in "expanded" mode (all data already loaded)
const isFullyLoaded = getCookie('sidebarTreeState') === 'expanded';
if (isFullyLoaded) {
// If we already have all the data, just expand all nodes visually
$('#sidebarContent ul').css({'display': 'block'});
$('#sidebarContent .iconToggle').removeClass('fa-angle-right');
$('#sidebarContent .iconToggle').addClass('fa-angle-down');
} else {
// Otherwise, we need to expand nodes one by one with lazy loading
// First, get all collapsed nodes with toggle icons
const collapsedNodes = $('#sidebarContent .iconToggle.fa-angle-right').toArray();
// Define a recursive function to expand nodes one by one
function expandNextNode(index) {
if (index >= collapsedNodes.length) {
return; // All nodes expanded
}
const toggleIcon = collapsedNodes[index];
// Trigger a click on the toggle icon to expand it (which will make an AJAX call)
$(toggleIcon).trigger('click');
// Wait for the AJAX call to complete before expanding the next node
// We'll use a timeout to give the AJAX call time to complete
setTimeout(function() {
expandNextNode(index + 1);
}, 100);
}
// Start expanding nodes
expandNextNode(0);
}
}
// Right click
$(function(){
if($('#sidebarContent').length) {
$('#sidebarContent').contextMenu({
selector: 'a',
callback: function(key, options) {
handleContextMenuClick(key, this);
},
items: {
"run": {name: "Run",
icon: "fa-play-circle-o",
visible: function(key, opt) { return showRunnablePageItems(opt); }
},
"runNewTab": {name: "Run in New Tab",
icon: "fa-play-circle-o",
visible: function(key, opt) { return showRunnablePageItems(opt); },
className: "contextmenu-newtab"
},
"sep0": {type: "cm_separator", visible: function(key, opt) { return showRunnablePageItems(opt); }
},
"edit": {name: "Edit", icon: "fa-edit"},
"editNewTab": {name: "Edit in New Tab", icon: "fa-edit", className: "contextmenu-newtab"},
"rename": {name: "Rename", icon: "fa-pencil"},
"move": {name: "Move", icon: "fa-long-arrow-right"},
"delete": {name: "Delete", icon: "fa-trash-o"},
"sep1": {type: "cm_separator"},
"fold1": {
name: "Add",
icon: "fa-plus",
items: {
addStatic: {name: "Static Page", icon: "fa-file-o"},
addSuite: {name: "Suite Page", icon: "fa-cogs"},
addTest: {name: "Test Page", icon: "fa-cog"}
}
},
"sep2": {type: "cm_separator"},
"copypath": {name: "Copy Page Path", icon: "fa-clipboard"},
"setSidebarRoot": {name: "Set as Sidebar Root", icon: "fa-thumb-tack"},
"testhistory": {name: "Test History",
icon: "fa-history",
visible: function(key, opt) {
return showRunnablePageItems(opt);
}},
"search": {name: "Search From Here", icon: "fa-search"},
"properties": {name: "Properties", icon:"fa-ellipsis-h"}
}
});
}
});
function showRunnablePageItems(opt) {
if (opt.$trigger[0].classList.contains('test') === false && opt.$trigger[0].classList.contains('suite') === false) {
return false;
}
return true;
}
function handleContextMenuClick(key, element) {
if (key === 'copypath') {
copyToClipboard(element[0].pathname.replace('/', '.'));
} else if (key === 'setSidebarRoot') {
var exp = new Date();
exp.setTime(exp.getTime() + 3600*1000*24*365);
document.cookie = 'sidebarRoot=/' + element[0].pathname.replace('/', '.').substring(1) + ';expires=' + exp.toGMTString() + ';path=/';
getSidebarContent(placeEverythingForSidebar);
$("#resetSidebarRoot").remove();
if (!$("#resetSidebarRoot").is(":visible")) {
$(".buttonSidebarDiv").append('');
}
//Manually register onClick handler
$('#resetSidebarRoot').on('click', function () {
document.cookie = 'sidebarRoot= ; expires = Thu, 01 Jan 1970 00:00:00 GMT';
$('#sidebarContent').empty();
$('#sidebarContent').append('');
getSidebarContent(placeEverythingForSidebar);
$(this).remove();
});
} else {
var responder = getResponder(key, element);
if (key.includes('NewTab')) {
window.open(element[0].pathname + '?' +responder, '_blank');
} else {
window.location.href = element[0].pathname + '?' + responder;
}
}
}
function getResponder(key, element) {
var el = element[0];
switch(key) {
case "run":
case "runNewTab":
return el.classList.contains('suite') ? 'suite' : 'test';
case "edit":
case "editNewTab":
return 'edit';
case "rename":
return 'refactor&type=rename';
case "move":
return 'refactor&type=move';
case "delete":
return 'deletePage';
case "testhistory":
return 'testHistory';
case "search":
return 'search';
case "properties":
return 'properties';
case "addStatic":
return 'new&pageTemplate=.TemplateLibrary.StaticPage';
case "addSuite":
return 'new&pageTemplate=.TemplateLibrary.SuitePage';
case "addTest":
return 'new&pageTemplate=.TemplateLibrary.TestPage';
}
}
/*
SIDEBAR FUNCTIONS END
|
PAGE HISTORY START
*/
function getPageHistory(url, callback) {
$.ajax({
type: 'GET',
url: url,
contentType: 'charset=utf-8',
success: data => callback(data),
error: function (xhr) {
console.log('Error code: ' + xhr.status, xhr);
}
});
}
function generateTestHistoryTable(data) {
const check = document.getElementById('recentTestHistoryTable');
if (check !== undefined) {
const parser = new DOMParser();
let parserhtml = parser.parseFromString(data, 'text/html');
let table = parserhtml.getElementsByTagName('table')[0];
const rows = table.getElementsByTagName('tr');
// Make row length no longer than 5
if (rows.length > 5) {
let rowNumberToSlice = rows.length - 5;
$(rows, 'tr').slice(-rowNumberToSlice).remove();
}
// Make new column named "last 5 results"
let resultsReportTd = rows[0].childNodes[9];
resultsReportTd.innerText = 'Last 5 Results';
resultsReportTd.setAttribute('colspan', 5);
// Make cell length from column "last 5 results" no longer than 5
for (let i = 1; i < rows.length; i++) {
let cells = rows[i].getElementsByTagName('td');
// 4 columns + 5 cells
if (cells.length > 9) {
$(cells, 'td').slice(9).remove();
}
}
check.appendChild(table);
}
}
/*
PAGE HISTORY END
|
FITNESSE TOOLTIPS START
*/
// Get list of tooltips
function getToolTips(callback) {
// get data from responder
$.ajax({
type: 'GET',
url: location.protocol + '//' + window.location.hostname + ':' + window.location.port + '/?Tooltips',
contentType: 'charset=utf-8',
success: data => callback(data),
error: function (xhr) {
console.log('Error code: ' + xhr.status, xhr);
}
});
}
// Places picked tooltips on the page
function placeToolTip(text) {
if ($('#tooltip-text')) {
if (text.includes('') && !text.includes('