Proje dosyaları eklendi
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
document.querySelector('[data-token-toggle]')?.addEventListener('click', (event) => {
|
||||
const input = document.querySelector('#newApiKeyValue');
|
||||
const showing = input.type === 'text';
|
||||
input.type = showing ? 'password' : 'text';
|
||||
event.currentTarget.textContent = showing ? event.currentTarget.dataset.show : event.currentTarget.dataset.hide;
|
||||
});
|
||||
|
||||
document.querySelector('[data-token-copy]')?.addEventListener('click', async (event) => {
|
||||
const value = document.querySelector('#newApiKeyValue')?.value;
|
||||
if (!value) return;
|
||||
await navigator.clipboard.writeText(value);
|
||||
event.currentTarget.textContent = event.currentTarget.dataset.copied;
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-status-switch]').forEach((button) => button.closest('form').addEventListener('submit', () => {
|
||||
button.disabled = true;
|
||||
}));
|
||||
|
||||
document.querySelectorAll('[data-action-key]').forEach((button) => button.addEventListener('click', () => {
|
||||
const form = document.querySelector('[data-action-form]');
|
||||
const message = document.querySelector('[data-action-message]');
|
||||
const submit = document.querySelector('[data-action-submit]');
|
||||
form.action = button.dataset.url;
|
||||
document.querySelector('[data-action-method]').innerHTML = button.dataset.kind === 'delete' ? '<input type="hidden" name="_method" value="DELETE">' : '';
|
||||
message.textContent = (button.dataset.kind === 'delete' ? message.dataset.deleteTemplate : message.dataset.revokeTemplate).replace(':name', button.dataset.name);
|
||||
submit.textContent = button.textContent.trim();
|
||||
document.querySelector('[data-action-title]').textContent = button.textContent.trim();
|
||||
}));
|
||||
@@ -0,0 +1,72 @@
|
||||
import './bootstrap';
|
||||
import 'bootstrap';
|
||||
import './api-keys';
|
||||
import '../css/api-keys.css';
|
||||
import '../css/api-key-status.css';
|
||||
|
||||
const icons = { light: '☀', dark: '☾', system: '◐' };
|
||||
|
||||
function updateThemeControls(event) {
|
||||
const state = event?.detail ?? {
|
||||
preference: window.ThemeManager.getPreference(),
|
||||
resolved: window.ThemeManager.getResolvedTheme(),
|
||||
};
|
||||
|
||||
document.querySelectorAll('[data-theme-value]').forEach((button) => {
|
||||
const active = button.dataset.themeValue === state.preference;
|
||||
button.classList.toggle('active', active);
|
||||
button.setAttribute('aria-pressed', String(active));
|
||||
button.setAttribute('aria-current', active ? 'true' : 'false');
|
||||
const check = button.querySelector('[data-theme-check]');
|
||||
if (check) check.hidden = !active;
|
||||
});
|
||||
document.querySelectorAll('[data-theme-current-label]').forEach((node) => {
|
||||
node.textContent = document.querySelector(`[data-theme-value="${state.preference}"]`)?.dataset.themeLabel ?? state.preference;
|
||||
});
|
||||
document.querySelectorAll('[data-theme-current-icon]').forEach((node) => {
|
||||
node.textContent = icons[state.preference];
|
||||
});
|
||||
document.querySelectorAll('[data-theme-preference]').forEach((node) => {
|
||||
node.textContent = document.querySelector(`[data-theme-value="${state.preference}"]`)?.dataset.themeLabel ?? state.preference;
|
||||
});
|
||||
document.querySelectorAll('[data-theme-resolved]').forEach((node) => {
|
||||
node.textContent = document.querySelector(`[data-theme-value="${state.resolved}"]`)?.dataset.themeLabel ?? state.resolved;
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const button = event.target.closest('[data-theme-value]');
|
||||
if (button) {
|
||||
window.ThemeManager.setPreference(button.dataset.themeValue);
|
||||
}
|
||||
});
|
||||
document.querySelector('[data-settings-theme]')?.addEventListener('change', event => window.ThemeManager.setPreference(event.target.value));
|
||||
if (document.querySelector('input[name="reduced_motion"]:checked')) document.documentElement.dataset.reducedMotion = 'true';
|
||||
|
||||
window.addEventListener('trafficpumper:theme-change', updateThemeControls);
|
||||
updateThemeControls();
|
||||
|
||||
function renderRankingCharts() {
|
||||
document.querySelectorAll('[data-ranking-chart]').forEach((container) => {
|
||||
const series = JSON.parse(container.dataset.rankingChart || '[]');
|
||||
const colors = ['#6d5dfc', '#16a085', '#e45175', '#e6a23c', '#2d8cf0', '#8e44ad'];
|
||||
const width = 900, height = 320, pad = 38;
|
||||
const points = series.flatMap((item) => item.points);
|
||||
if (!points.length) return;
|
||||
const dates = points.map((point) => new Date(point.date).getTime());
|
||||
const minDate = Math.min(...dates), maxDate = Math.max(...dates);
|
||||
const x = (date) => pad + ((new Date(date).getTime() - minDate) / Math.max(1, maxDate - minDate)) * (width - pad * 2);
|
||||
const y = (rank) => pad + ((rank - 1) / 99) * (height - pad * 2);
|
||||
const grid = [1, 10, 20, 50, 100].map((rank) => `<line x1="${pad}" y1="${y(rank)}" x2="${width-pad}" y2="${y(rank)}"/><text x="4" y="${y(rank)+4}">${rank}</text>`).join('');
|
||||
const paths = series.map((item, index) => {
|
||||
const coordinates = item.points.map((point) => `${x(point.date)},${y(point.rank)}`).join(' ');
|
||||
const dots = item.points.map((point) => `<circle cx="${x(point.date)}" cy="${y(point.rank)}" r="4"><title>${item.keyword} · ${item.project} · #${point.rank} · ${new Date(point.date).toLocaleString()}</title></circle>`).join('');
|
||||
return `<g style="--series:${colors[index%colors.length]}"><polyline points="${coordinates}"/>${dots}</g>`;
|
||||
}).join('');
|
||||
const legend = series.map((item,index)=>`<span><i style="background:${colors[index%colors.length]}"></i>${item.keyword}</span>`).join('');
|
||||
container.innerHTML = `<svg viewBox="0 0 ${width} ${height}" aria-hidden="true"><g class="tp-chart-grid">${grid}</g>${paths}</svg><div class="tp-chart-legend">${legend}</div>`;
|
||||
});
|
||||
}
|
||||
renderRankingCharts();
|
||||
window.addEventListener('trafficpumper:theme-change', renderRankingCharts);
|
||||
function renderVisibilityCharts(){document.querySelectorAll('[data-visibility-chart]').forEach(container=>{const data=JSON.parse(container.dataset.visibilityChart||'{}');const all=[...(data.current||[]),...(data.previous||[])];if(!all.length)return;const w=900,h=300,p=35;const dates=all.map(x=>new Date(x.date).getTime()),min=Math.min(...dates),max=Math.max(...dates);const x=d=>p+(new Date(d).getTime()-min)/Math.max(1,max-min)*(w-p*2),y=v=>h-p-(v/100)*(h-p*2);const series=(points,color,dash='')=>`<polyline style="stroke:${color};stroke-dasharray:${dash}" points="${points.map(q=>`${x(q.date)},${y(q.score)}`).join(' ')}"/>`+points.map(q=>`<circle style="--series:${color}" cx="${x(q.date)}" cy="${y(q.score)}" r="4"><title>${q.date} · ${q.score}% · ${q.measured}</title></circle>`).join('');container.innerHTML=`<svg viewBox="0 0 ${w} ${h}"><g class="tp-chart-grid">${[0,25,50,75,100].map(v=>`<line x1="${p}" y1="${y(v)}" x2="${w-p}" y2="${y(v)}"/><text x="2" y="${y(v)+4}">${v}%</text>`).join('')}</g>${series(data.current||[],'#6d5dfc')}${series(data.previous||[],'#16a085','7 5')}</svg>`;});}renderVisibilityCharts();window.addEventListener('trafficpumper:theme-change',renderVisibilityCharts);
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
@@ -0,0 +1,56 @@
|
||||
(function (window, document) {
|
||||
'use strict';
|
||||
var STORAGE_KEY = 'trafficpumper.theme';
|
||||
var DEFAULT_PREFERENCE = 'system';
|
||||
var VALID_PREFERENCES = ['light', 'dark', 'system'];
|
||||
var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
var accountTheme = typeof document.querySelector === 'function' ? document.querySelector('meta[name="trafficpumper-account-theme"]')?.content : null;
|
||||
var preference = VALID_PREFERENCES.indexOf(accountTheme) !== -1 ? accountTheme : readPreference();
|
||||
try { window.localStorage.setItem(STORAGE_KEY, preference); } catch (error) {}
|
||||
if (typeof document.querySelector === 'function' && document.querySelector('meta[name="trafficpumper-reduced-motion"]')?.content === 'true') document.documentElement.dataset.reducedMotion = 'true';
|
||||
|
||||
function isValid(value) { return VALID_PREFERENCES.indexOf(value) !== -1; }
|
||||
function readPreference() {
|
||||
try {
|
||||
var stored = window.localStorage.getItem(STORAGE_KEY);
|
||||
if (isValid(stored)) return stored;
|
||||
window.localStorage.setItem(STORAGE_KEY, DEFAULT_PREFERENCE);
|
||||
return DEFAULT_PREFERENCE;
|
||||
} catch (error) { return DEFAULT_PREFERENCE; }
|
||||
}
|
||||
function resolve(value) {
|
||||
return value === 'system' ? (mediaQuery.matches ? 'dark' : 'light') : value;
|
||||
}
|
||||
function apply() {
|
||||
var resolved = resolve(preference);
|
||||
document.documentElement.setAttribute('data-bs-theme', resolved);
|
||||
document.documentElement.style.colorScheme = resolved;
|
||||
window.dispatchEvent(new window.CustomEvent('trafficpumper:theme-change', {
|
||||
detail: { preference: preference, resolved: resolved }
|
||||
}));
|
||||
return resolved;
|
||||
}
|
||||
function setPreference(value) {
|
||||
if (!isValid(value)) { return false; }
|
||||
preference = value;
|
||||
try { window.localStorage.setItem(STORAGE_KEY, value); } catch (error) {}
|
||||
apply();
|
||||
return true;
|
||||
}
|
||||
function onSystemThemeChange() {
|
||||
if (preference === 'system') { apply(); }
|
||||
}
|
||||
if (typeof mediaQuery.addEventListener === 'function') {
|
||||
mediaQuery.addEventListener('change', onSystemThemeChange);
|
||||
} else if (typeof mediaQuery.addListener === 'function') {
|
||||
mediaQuery.addListener(onSystemThemeChange);
|
||||
}
|
||||
window.ThemeManager = Object.freeze({
|
||||
storageKey: STORAGE_KEY,
|
||||
getPreference: function () { return preference; },
|
||||
getResolvedTheme: function () { return resolve(preference); },
|
||||
setPreference: setPreference,
|
||||
apply: apply
|
||||
});
|
||||
apply();
|
||||
}(window, document));
|
||||
Reference in New Issue
Block a user