Calculator

Calculater button on the corner of the screen, has dark/light theme switch and history.

目前為 2025-07-09 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Calculator
// @version      1.1
// @author       Mane
// @license     CC0-1.0
// @description  Calculater button on the corner of the screen, has dark/light theme switch and history.
// @match        *://*/*
// @run-at       document-end
// @namespace https://greasyfork.org/users/1491313
// ==/UserScript==

;(function(){
  'use strict';

  // --- KEYS ---
  const KEY_EXPR  = 'cc_expr';
  const KEY_HIST  = 'cc_hist';
  const KEY_THEME = 'cc_theme';
  const KEY_MEM   = 'cc_mem';

  // --- STATE ---
  let expr    = localStorage.getItem(KEY_EXPR) || '';
  let theme   = localStorage.getItem(KEY_THEME) || 'dark';
  let history = [];
  let mem     = parseFloat(localStorage.getItem(KEY_MEM) || '0');

  // --- STYLES ---
  const css = `
    :root {
      --bg:#1e1e1e;--fg:#f1f1f1;--btn-bg:#333;--btn-fg:#f1f1f1;--accent:#007acc;
    }
    [data-theme="light"]{
      --bg:#f1f1f1;--fg:#1e1e1e;--btn-bg:#ddd;--btn-fg:#1e1e1e;--accent:#005a9e;
    }
    #cc-toggle {
      position:fixed;bottom:20px;right:20px;width:36px;height:36px;
      background:var(--accent);color:#fff;border:none;border-radius:4px;
      cursor:pointer;z-index:999999;font-size:18px;
    }
    #cc-panel {
      position:fixed;bottom:60px;right:20px;width:320px;
      background:var(--bg);color:var(--fg);padding:10px;
      border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,.3);
      font-family:sans-serif;display:none;z-index:999999;
    }
    .small-btn {
      position:absolute;width:24px;height:24px;
      border:none;border-radius:4px;cursor:pointer;
      background:var(--btn-bg);color:var(--btn-fg);
      font-size:14px;line-height:1;
    }
    #cc-theme {top:8px;left:8px;}
    #cc-history-toggle {top:8px;right:8px;}
    #cc-display {
      flex:1;height:36px;
      padding:0 8px;font-size:18px;text-align:right;
      background:var(--btn-bg);color:var(--fg);border:none;
    }
    #cc-copy {
      margin-left:6px;
      background:var(--btn-bg);color:var(--btn-fg);
      border:none;border-radius:4px;cursor:pointer;
      font-size:18px;height:36px;width:36px;
    }
    .cc-row{display:flex;margin:4px 0}
    .cc-btn{
      flex:1;margin:2px;height:32px;
      background:var(--btn-bg);color:var(--btn-fg);
      border:none;border-radius:4px;cursor:pointer;
      font-size:16px;
    }
    .cc-op{background:var(--accent);color:#fff}
    #cc-history {
      display:none;margin-top:8px;
      max-height:140px;overflow:auto;
      background:var(--btn-bg);padding:6px;border-radius:4px;
    }
    #cc-history header{
      display:flex;justify-content:space-between;align-items:center;
      margin-bottom:4px;
    }
    #cc-history ul{
      list-style:none;padding:0;margin:0;font-size:14px;
    }
    #cc-history li{
      padding:2px 0;border-bottom:1px solid rgba(255,255,255,.1);
      cursor:pointer;
    }
    #cc-clear {
      width:100%;margin-top:4px;padding:4px;
      background:var(--btn-bg);color:var(--btn-fg);
      border:none;border-radius:4px;cursor:pointer;
    }
  `;
  const style = document.createElement('style');
  style.textContent = css;
  document.head.appendChild(style);

  // --- APPLY THEME ---
  document.documentElement.setAttribute('data-theme', theme);

  // --- BUILD UI ---
  const toggle = document.createElement('button');
  toggle.id = 'cc-toggle';
  toggle.textContent = '🖩';
  document.body.appendChild(toggle);

  const panel = document.createElement('div');
  panel.id = 'cc-panel';
  panel.innerHTML = `
    <button id="cc-theme" class="small-btn">${theme==='dark'?'☀️':'🌙'}</button>
    <button id="cc-history-toggle" class="small-btn">📜</button>
    <div style="display:flex;align-items:center;margin:32px 0 8px;">
      <input id="cc-display" type="text" />
      <button id="cc-copy" title="Copy">📋</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn cc-op">π</button>
      <button class="cc-btn cc-op">e</button>
      <button class="cc-btn cc-op">ln</button>
      <button class="cc-btn cc-op">exp</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn cc-op">^</button>
      <button class="cc-btn cc-op">%</button>
      <button class="cc-btn cc-op">!</button>
      <div style="flex:1;margin:2px"></div>
    </div>
    <div class="cc-row">
      <button class="cc-btn">M+</button>
      <button class="cc-btn">M-</button>
      <button class="cc-btn">MR</button>
      <button class="cc-btn">MC</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn">7</button><button class="cc-btn">8</button>
      <button class="cc-btn">9</button><button class="cc-btn cc-op">/</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn">4</button><button class="cc-btn">5</button>
      <button class="cc-btn">6</button><button class="cc-btn cc-op">*</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn">1</button><button class="cc-btn">2</button>
      <button class="cc-btn">3</button><button class="cc-btn cc-op">-</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn">0</button><button class="cc-btn">.</button>
      <button class="cc-btn cc-op">=</button><button class="cc-btn cc-op">+</button>
    </div>
    <div class="cc-row">
      <button class="cc-btn cc-op">(</button>
      <button class="cc-btn cc-op">)</button>
      <button class="cc-btn">C</button>
      <button class="cc-btn">√</button>
    </div>
    <div id="cc-history">
      <header>
        <strong>History</strong>
      </header>
      <ul></ul>
      <button id="cc-clear">Clear</button>
    </div>
  `;
  document.body.appendChild(panel);

  // --- ELEMENTS & INITIAL DISPLAY ---
  const disp = panel.querySelector('#cc-display');
  disp.value = expr;

  // --- EVENT HANDLERS ---
  toggle.addEventListener('click', () => {
    panel.style.display = panel.style.display==='block' ? 'none' : 'block';
  });

  panel.addEventListener('click', e => {
    const b = e.target, v = b.textContent;

    // theme toggle
    if (b.id === 'cc-theme') {
      theme = theme==='dark' ? 'light' : 'dark';
      document.documentElement.setAttribute('data-theme', theme);
      localStorage.setItem(KEY_THEME, theme);
      b.textContent = theme==='dark' ? '☀️' : '🌙';
      return;
    }

    // history panel
    if (b.id === 'cc-history-toggle') {
      const h = panel.querySelector('#cc-history');
      const opening = h.style.display !== 'block';
      h.style.display = opening ? 'block' : 'none';
      if (opening) {
        loadHistory();
        renderHistory();
      }
      return;
    }
    if (b.id === 'cc-clear') {
      history = [];
      saveHistory();
      renderHistory();
      return;
    }

    // copy
    if (b.id === 'cc-copy') {
      navigator.clipboard.writeText(expr).catch(console.error);
      return;
    }

    // memory functions
    if (['M+','M-','MR','MC'].includes(v)) {
      const current = parseFloat(expr) || 0;
      if (v === 'M+') mem += current;
      if (v === 'M-') mem -= current;
      if (v === 'MR') { expr = String(mem); updateDisplay(); }
      if (v === 'MC') mem = 0;
      localStorage.setItem(KEY_MEM, mem);
      return;
    }

    // calculator buttons
    if (b.classList.contains('cc-btn')) {
      if (v === 'C') {
        expr = '';
      } else if (v === '=') {
        evaluate();
      } else {
        expr += v;
      }
      updateDisplay();
    }
  });

  // paste into display
  disp.addEventListener('paste', e => {
    e.preventDefault();
    const txt = e.clipboardData.getData('text');
    expr = txt.replace(/[^\d+\-*/().√πeexpeln!%^sincotanlog]/g, '');
    updateDisplay();
  });

  // --- CALC LOGIC ---
  function evaluate(){
    try {
      const factorial = n => n < 2 ? 1 : n * factorial(n - 1);
      let safe = expr
        .replace(/√/g, 'Math.sqrt')
        .replace(/π/g, 'Math.PI')
        .replace(/\be\b/g, 'Math.E')
        .replace(/\bln\b/g, 'Math.log')
        .replace(/\bexp\b/g, 'Math.exp')
        .replace(/\^/g, '**')
        .replace(/(\d+)!/g, 'factorial($1)')
        .replace(/\b(sin|cos|tan|log)\b/g, 'Math.$1');
      const res = Function('Math','factorial','return '+safe)(Math, factorial);
      history.unshift({input: expr, result: res});
      history = history.slice(0, 50);
      saveHistory();
      expr = String(res);
    } catch {
      expr = 'Error';
    }
    updateDisplay();
  }

  function updateDisplay(){
    disp.value = expr;
    localStorage.setItem(KEY_EXPR, expr);
  }

  // --- HISTORY & STORAGE ---
  function loadHistory(){
    history = JSON.parse(localStorage.getItem(KEY_HIST) || '[]');
  }
  function saveHistory(){
    localStorage.setItem(KEY_HIST, JSON.stringify(history));
  }
  function renderHistory(){
    const ul = panel.querySelector('#cc-history ul');
    ul.innerHTML = history
      .map(it => `<li>${it.input} = ${it.result}</li>`)
      .join('');
    ul.querySelectorAll('li').forEach((li, i) => {
      li.addEventListener('click', () => {
        expr = history[i].input;
        updateDisplay();
      });
    });
  }

})();