Pinned Loading
-
β‘Dynamics 365 Power Hub
β‘Dynamics 365 Power Hub 1javascript: (function () {
2if (document.getElementById('bm-modal-gmn')) return;
3const w = window;
4const l = w.location;
5console.log(l);
-
Bookmarklet Modal with Jira Ticket H...
Bookmarklet Modal with Jira Ticket History 1javascript: (function () { function normalizeTimestampToMinute(timestamp) { const date = new Date(timestamp); date.setSeconds(0, 0); return date.getTime(); } function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleDateString('en-GB', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } function calculateTimespan(startDate, endDate) { const start = new Date(startDate); const end = new Date(endDate); const diffMs = end.getTime() - start.getTime(); const diffMinutes = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays > 0) { const hours = Math.floor((diffMs % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); if (hours > 0) return `${diffDays}d ${hours}h`; return `${diffDays} day${diffDays > 1 ? %27s%27 : %27%27}`; } else if (diffHours > 0) { const minutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60)); if (minutes > 0) return `${diffHours}h ${minutes}m`; return `${diffHours} hour${diffHours > 1 ? %27s%27 : %27%27}`; } else if (diffMinutes > 0) { return `${diffMinutes} minute${diffMinutes > 1 ? %27s%27 : %27%27}`; } else { return %27Less than 1 minute%27; } } function generateCSV(data, issueKey) { if (!data || !data.changelog || !data.changelog.histories) return %27%27; const allowedFields = [%27Support Team%27, %27assignee%27, %27status%27]; let csvContent = %27Issue Key,Date,Time,Author,Field,From,To,Timespan to Next\n%27; let allActivities = []; data.changelog.histories.forEach(history => { if (history.items) { history.items.forEach(item => { if (allowedFields.includes(item.field)) { allActivities.push({ ...item, created: history.created, author: history.author, timestamp: new Date(history.created).getTime() }); } }); } }); allActivities.sort((a, b) => a.timestamp - b.timestamp); allActivities.forEach((activity, index) => { const date = new Date(activity.created); const dateStr = date.toLocaleDateString(%27en-GB%27); const timeStr = date.toLocaleTimeString(%27en-GB%27, { hour: %272-digit%27, minute: %272-digit%27 }); const fromValue = activity.fromString || %27%27; const toValue = activity.toString || %27%27; let timespan = %27%27; if (index < allActivities.length - 1) { timespan = calculateTimespan(activity.created, allActivities[index + 1].created); } csvContent += `"${issueKey}","${dateStr}","${timeStr}","${activity.author.displayName}","${activity.field}","${fromValue}","${toValue}","${timespan}"\n`; }); return csvContent; } function downloadCSV(content, filename) { const blob = new Blob([content], { type: %27text/csv;charset=utf-8;%27 }); const link = document.createElement(%27a%27); if (link.download !== undefined) { const url = URL.createObjectURL(blob); link.setAttribute(%27href%27, url); link.setAttribute(%27download%27, filename); link.style.visibility = %27hidden%27; document.body.appendChild(link); link.click(); document.body.removeChild(link); } } function getFieldStyle(field) { const styles = { %27status%27: { icon: %27π%27, color: %27#3498db', bgColor: '#e3f2fd' }, 'assignee': { icon: 'π€', color: '#e74c3c', bgColor: '#ffebee' }, 'Support Team': { icon: 'π’', color: '#9c27b0', bgColor: '#f3e5f5' } }; return styles[field] || { icon: 'π', color: '#95a5a6', bgColor: '#f5f5f5' }; } async function extractJiraData() { try { const pathSegments = window.location.pathname.split('/'); let issueKey = null; for (let i = pathSegments.length - 1; i >= 0; i--) { const segment = pathSegments[i]; const match = segment.match(/^[A-Z]+-\d+$/); if (match) { issueKey = match[0]; break; } } if (!issueKey) { throw new Error('Could not extract issue key from URL. Make sure you are on a Jira issue page.'); } console.log(%60π Extracted issue key: ${issueKey}%60); const apiUrl = %60${window.location.origin}/rest/api/3/issue/${issueKey}?expand=changelog&fields=id,changelog%60; console.log(%60π‘ Fetching data from: ${apiUrl}%60); const response = await fetch(apiUrl, { method: 'GET', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, credentials: 'same-origin' }); if (!response.ok) { throw new Error(%60API request failed: ${response.status} ${response.statusText}%60); } const data = await response.json(); console.log(%60β Successfully fetched data for ${issueKey}:%60, data); if (!data.key) { data.key = issueKey; } return data; } catch (error) { console.error('β Error extracting Jira data:', error); throw error; } } function generateTimeline(data) { if (!data || !data.changelog || !data.changelog.histories) { return '<div style=\"padding: 20px; text-align: center;\">No timeline data available</div>'; } const issueKey = data.key || 'Unknown Issue'; let allActivities = []; let fieldGroups = {}; const allowedFields = ['Support Team', 'assignee', 'status']; data.changelog.histories.forEach(history => { if (history.items) { history.items.forEach(item => { if (allowedFields.includes(item.field)) { const activity = { ...item, created: history.created, author: history.author, historyId: history.id, timestamp: new Date(history.created).getTime() }; allActivities.push(activity); if (!fieldGroups[item.field]) fieldGroups[item.field] = []; fieldGroups[item.field].push(activity); } }); } }); allActivities.sort((a, b) => a.timestamp - b.timestamp); Object.keys(fieldGroups).forEach(fieldType => { fieldGroups[fieldType].sort((a, b) => a.timestamp - b.timestamp); }); if (allActivities.length === 0) { return '<div style=\"padding: 20px; text-align: center;\">No activities found for timeline</div>'; } const desiredFields = ['Support Team', 'assignee', 'status']; const fieldTypes = desiredFields.filter(field => fieldGroups[field] && fieldGroups[field].length > 0); const displayedActivities = []; fieldTypes.forEach(fieldType => { if (fieldGroups[fieldType]) { fieldGroups[fieldType].forEach(activity => { displayedActivities.push(activity); }); } }); const displayedActivitiesWithNormalizedTime = displayedActivities.map(activity => ({ ...activity, normalizedTimestamp: normalizeTimestampToMinute(activity.timestamp) })); displayedActivitiesWithNormalizedTime.sort((a, b) => a.timestamp - b.timestamp); const uniqueTimestamps = [...new Set(displayedActivitiesWithNormalizedTime.map(a => a.normalizedTimestamp))].sort((a, b) => a - b); const timestampPositions = new Map(); let currentPos = 60; uniqueTimestamps.forEach((timestamp, index) => { timestampPositions.set(timestamp, currentPos); const totalDisplayedActivities = displayedActivities.length; const activitiesAtTimestamp = displayedActivitiesWithNormalizedTime.filter(a => a.normalizedTimestamp === timestamp).length; let baseSpacing; if (totalDisplayedActivities > 20) { baseSpacing = Math.max(150, Math.min(250, Math.floor(2000 / Math.max(1, totalDisplayedActivities)))); } else if (totalDisplayedActivities > 10) { baseSpacing = Math.max(140, Math.min(220, Math.floor(1500 / Math.max(1, totalDisplayedActivities)))); } else { baseSpacing = Math.max(120, Math.min(200, Math.floor(1200 / Math.max(1, totalDisplayedActivities)))); } const timestampSpacing = activitiesAtTimestamp > 3 ? Math.max(120, Math.floor(baseSpacing * 1.3)) : baseSpacing; currentPos += timestampSpacing; }); const maxPosition = Math.max(...Array.from(timestampPositions.values())); const dynamicHeight = Math.max(800, maxPosition + 200); let html = %60<div style=\"font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; background: white; border-radius: 10px; box-shadow: 0 2px 20px rgba(0,0,0,0.1); overflow: hidden; width: 100%; margin: 20px auto;\"><div style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center;\"><h1 style=\"margin: 0; font-size: 2em; font-weight: 300;\">Activity Timeline</h1><p style=\"margin: 10px 0 0 0; opacity: 0.9;\"><a href=\"${window.location.origin}/rest/api/3/issue/${issueKey}?expand=changelog&fields=id,changelog\" target=\"_blank\" style=\"color: white; text-decoration: none; border-bottom: 1px solid rgba(255,255,255,0.5); transition: all 0.3s ease;\" onmouseover=\"this.style.borderBottomColor='white';\" onmouseout=\"this.style.borderBottomColor='rgba(255,255,255,0.5)';\">${issueKey}</a> - Generated by Timeline Bookmarklet</p></div><div style=\"display: flex; padding: 40px 20px; gap: 20px;\">%60; fieldTypes.forEach(fieldType => { const style = getFieldStyle(fieldType); const items = fieldGroups[fieldType]; html += %60<div style=\"flex: 1; position: relative; min-height: ${dynamicHeight}px;\"><div style=\"background: ${style.color}; color: white; padding: 15px; border-radius: 8px 8px 0 0; font-weight: 600; display: flex; align-items: center; gap: 10px;\"><span style=\"font-size: 1.3em;\">${style.icon}</span>${fieldType.charAt(0).toUpperCase() + fieldType.slice(1)}</div><div style=\"background: ${style.bgColor}; border-radius: 0 0 8px 8px; border: 2px solid ${style.color}; border-top: none; position: relative; min-height: ${dynamicHeight - 100}px; padding: 60px 20px 20px 20px;\">%60; if (items.length === 0) { html += '<div style=\"text-align: center; padding: 40px; color: #999; font-style: italic;\">No changes recorded</div>'; } else { items.forEach((item, itemIndex) => { const normalizedTimestamp = normalizeTimestampToMinute(item.timestamp); const topPosition = timestampPositions.get(normalizedTimestamp) || 60; html += %60<div style=\"position: absolute; left: 20px; right: 20px; top: ${topPosition}px; transform: translateY(-50%);\"><div style=\"background: white; border: 2px solid ${style.color}; border-radius: 10px; padding: 18px; box-shadow: 0 4px 16px rgba(0,0,0,0.08);\"><div style=\"font-size: 0.8em; color: #666; font-weight: 600; margin-bottom: 5px;\">${formatDate(item.created)}</div><div style=\"font-size: 0.75em; color: #888; font-style: italic; margin-bottom: 10px;\">by ${item.author.displayName}</div><div style=\"font-size: 0.9em;\">${item.fromString ? %60<span style=\"color: #e74c3c; text-decoration: line-through; opacity: 0.8; display: block; font-size: 0.85em; margin-bottom: 3px;\">${item.fromString}</span>%60 : ''} <span style=\"color: ${style.color}; font-weight: 600;\">${item.toString}</span></div></div><div style=\"position: absolute; right: -6px; top: 50%; transform: translateY(-50%); width: 12px; height: 12px; background: ${style.color}; border: 3px solid white; border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.2);\"></div></div>%60; const currentActivityIndex = displayedActivitiesWithNormalizedTime.findIndex(a => a.timestamp === item.timestamp && a.field === item.field); if (currentActivityIndex >= 0 && currentActivityIndex < displayedActivitiesWithNormalizedTime.length - 1) { const nextActivity = displayedActivitiesWithNormalizedTime[currentActivityIndex + 1]; const nextNormalizedTimestamp = normalizeTimestampToMinute(nextActivity.timestamp); const nextTopPosition = timestampPositions.get(nextNormalizedTimestamp) || 60; const actualLineHeight = nextTopPosition - topPosition - 70; if (actualLineHeight > 0) { const timespan = calculateTimespan(item.created, nextActivity.created); const displayLineHeight = Math.max(40, actualLineHeight); const lineMidPoint = actualLineHeight > 40 ? 50 : (actualLineHeight / 2); const currentFieldIndex = fieldTypes.indexOf(fieldType); const nextFieldIndex = fieldTypes.indexOf(nextActivity.field); const isNextInDifferentLane = nextFieldIndex !== currentFieldIndex; if (isNextInDifferentLane) { const laneDirection = nextFieldIndex > currentFieldIndex ? 'right' : 'left'; const laneDistance = Math.abs(nextFieldIndex - currentFieldIndex); const nextFieldStyle = getFieldStyle(nextActivity.field); html += %60<div style=\"position: absolute; left: 75%; top: ${topPosition + 70}px; height: ${displayLineHeight}px; z-index: 1;\"><div style=\"position: absolute; left: 0; top: 0; width: 2px; height: 95%; background: repeating-linear-gradient(to bottom, ${style.color} 0px, ${style.color} 4px, transparent 4px, transparent 8px); opacity: 0.6;\"></div><div style=\"position: absolute; left: -3px; top: 95%; width: 8px; height: 8px; background: ${nextFieldStyle.color}; transform: rotate(45deg); opacity: 0.8;\"></div><div style=\"position: absolute; left: 50%; top: ${lineMidPoint}%; transform: translate(-50%, -50%); background: white; border: 1px solid ${style.color}; border-radius: 12px; padding: 4px 8px; font-size: 0.7em; font-weight: 600; color: ${style.color}; white-space: nowrap; z-index: 2; box-shadow: 0 2px 8px rgba(0,0,0,0.15);\">${timespan}</div></div>%60; } else { html += %60<div style=\"position: absolute; left: 75%; transform: translateX(-50%); top: ${topPosition + 70}px; height: ${displayLineHeight}px; width: 2px; background: repeating-linear-gradient(to bottom, ${style.color} 0px, ${style.color} 4px, transparent 4px, transparent 8px); opacity: 0.6; z-index: 1;\"><div style=\"position: absolute; left: 50%; top: ${lineMidPoint}%; transform: translate(-50%, -50%); background: white; border: 1px solid ${style.color}; border-radius: 12px; padding: 4px 8px; font-size: 0.7em; font-weight: 600; color: ${style.color}; white-space: nowrap; z-index: 2; box-shadow: 0 2px 8px rgba(0,0,0,0.15);\">${timespan}</div></div>%60; } } } }); } html += '</div></div>'; }); html += %60</div><div style=\"background: #f8f9fa; padding: 30px; text-align: center; border-top: 1px solid #eee;\"><h3 style=\"margin: 0 0 20px 0; color: #333;\">Activity Summary</h3><div style=\"display: inline-block; margin: 0 20px; padding: 15px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);\"><span style=\"font-size: 2em; font-weight: bold; color: #667eea; display: block;\">${allActivities.length}</span><span style=\"font-size: 0.9em; color: #666; text-transform: uppercase; letter-spacing: 1px;\">Total Changes</span></div><div style=\"margin-top: 20px;\"><button id=\"download-csv-btn\" style=\"background: #28a745; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; box-shadow: 0 2px 10px rgba(40,167,69,0.3); transition: all 0.3s ease;\">π Download CSV</button></div></div></div>%60; return html; } async function executeBookmarklet() { try { console.log('π Timeline Bookmarklet Starting...'); const modal = document.createElement('div'); modal.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 10000; display: flex; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box;'; const content = document.createElement('div'); content.style.cssText = 'background: white; border-radius: 10px; width: 80vw; max-height: 90vh; overflow: auto; position: relative; padding: 40px; text-align: center;'; content.innerHTML = '<div style=\"font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif;\"><div style=\"font-size: 2em; margin-bottom: 20px;\">β³</div><h2 style=\"margin: 0 0 10px 0; color: #333;\">Loading Timeline</h2><p style=\"color: #666; margin: 0;\">Fetching issue data from Jira API...</p></div>'; const closeBtn = document.createElement('button'); closeBtn.innerHTML = 'β'; closeBtn.style.cssText = 'position: absolute; top: 10px; right: 10px; background: #ff4757; color: white; border: none; border-radius: 50%; width: 30px; height: 30px; cursor: pointer; z-index: 10001; font-size: 16px;'; closeBtn.onclick = () => document.body.removeChild(modal); modal.onclick = (e) => { if (e.target === modal) document.body.removeChild(modal); }; content.onclick = (e) => e.stopPropagation(); content.appendChild(closeBtn); modal.appendChild(content); document.body.appendChild(modal); const jiraData = await extractJiraData(); const timelineHtml = generateTimeline(jiraData); content.innerHTML = timelineHtml; content.appendChild(closeBtn); const csvBtn = document.getElementById('download-csv-btn'); if (csvBtn) { csvBtn.onclick = () => { const csvContent = generateCSV(jiraData, jiraData.key); const filename = %60${jiraData.key}_timeline_${new Date().toISOString().split('T')[0]}.csv%60; downloadCSV(csvContent, filename); csvBtn.innerHTML = 'β Downloaded!'; setTimeout(() => { csvBtn.innerHTML = 'π Download CSV'; }, 2000); }; } console.log('β Timeline generated successfully!'); } catch (error) { console.error('β Timeline Bookmarklet Error:', error); const existingModal = document.querySelector('[style*=\"z-index: 10000\"]'); if (existingModal) { const content = existingModal.querySelector('div'); if (content) { content.innerHTML = %60<div style=\"font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; padding: 20px;\"><div style=\"font-size: 2em; margin-bottom: 20px; color: #e74c3c;\">β</div><h2 style=\"margin: 0 0 15px 0; color: #e74c3c;\">Error Generating Timeline</h2><p style=\"color: #666; margin-bottom: 15px; line-height: 1.5;\">${error.message}</p><div style=\"background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px; padding: 15px; margin-top: 20px;\"><h4 style=\"margin: 0 0 10px 0; color: #495057;\">Troubleshooting Tips:</h4><ul style=\"text-align: left; color: #6c757d; margin: 0; padding-left: 20px;\"><li>Make sure you're on a Jira issue page</li><li>Check if you're logged into Jira</li><li>Verify the issue exists and you have permission to view it</li><li>Try refreshing the page and running the bookmarklet again</li></ul></div></div>%60; const closeBtn = document.createElement('button'); closeBtn.innerHTML = 'β'; closeBtn.style.cssText = 'position: absolute; top: 10px; right: 10px; background: #ff4757; color: white; border: none; border-radius: 50%; width: 30px; height: 30px; cursor: pointer; z-index: 10001; font-size: 16px;'; closeBtn.onclick = () => document.body.removeChild(existingModal); content.appendChild(closeBtn); } } else { alert(%60Error generating timeline: ${error.message}\\n\\nMake sure you're on a Jira issue page and logged in.%60); } } } executeBookmarklet(); })(); -
Excel.Lambda
Excel.Lambda 1IFBLANK = LAMBDA(value, value_if_blank,
2IF(
3ISBLANK(value),
4value_if_blank,
5value
-
FizzBuzz in Rust
FizzBuzz in Rust 1#[derive(Debug)]2enum Words {
3Fizz = 3,
4Buzz = 5,
5}
Something went wrong, please refresh the page to try again.
If the problem persists, check the GitHub status page or contact support.
If the problem persists, check the GitHub status page or contact support.