SOURCE

console 命令行工具 X clear

                    
>
console
let currentSelectedChatId = null;
let renderStartOffset = 0;
const PAGE_SIZE = 20;
let forceFullRenderMode = false;

export function renderUI(messageMap = {}) {
  const overlay = document.getElementById('lineOverlay');
  const contactsEl = document.getElementById('lineContacts');
  const msgEl = document.getElementById('lineMessages');
  const splitter = document.getElementById('lineSplitter');
  const ball = document.getElementById('lineBall');

if (!overlay || !contactsEl || !msgEl) return;

// ✅ 保证控件初始化始终执行
enableDrag(overlay);
enableSplitter(splitter, contactsEl);
restoreState();

  contactsEl.innerHTML = '';
  msgEl.innerHTML = '点击左侧头像查看';

  const chatIds = Object.keys(messageMap);
  if (chatIds.length === 0) {
    msgEl.innerHTML = '<p>暂无消息</p>';
    currentSelectedChatId = null;
    return;
  }

  chatIds.forEach((chatId, index) => {
    const chat = messageMap[chatId];
    const wrapper = document.createElement("div");
    wrapper.className = "contact";

    const avatar = document.createElement('img');
    avatar.src = chat?.avatar || 'https://via.placeholder.com/40';
    avatar.title = chat.name;
    avatar.dataset.chatId = chatId;

    const nameSpan = document.createElement('span');
    nameSpan.className = 'name';
    nameSpan.innerText = chat.name;

    const badge = document.createElement("span");
    badge.className = "badge";
    badge.innerText = getUnreadCountFromDOM(chatId);

    avatar.onclick = () => {
      currentSelectedChatId = chatId;
      forceFullRenderMode = false;
      renderMessages(chatId, chat.name, chat.messages || [], msgEl, messageMap);
      highlightCurrentAvatar(contactsEl, chatId);
    };

    wrapper.appendChild(avatar);
    wrapper.appendChild(nameSpan);
    wrapper.appendChild(badge);
    contactsEl.appendChild(wrapper);

    if (!currentSelectedChatId) {
      avatar.click();
    }
  });

  document.getElementById('markReadBtn').onclick = () => {
    if (!currentSelectedChatId) return alert('⚠️ 未选择联系人');
    document.querySelector(`img[data-chat-id="${currentSelectedChatId}"]`).nextSibling.nextSibling.innerText = '';
  };

  document.getElementById('lineRefresh').onclick = () => {
    chrome.storage.local.get("messages", res => {
      renderUI(res.messages || {});
    });
  };

  document.getElementById("lineMinimize").onclick = () => {
    overlay.style.display = "none";
    ball.style.display = "flex";
    saveState();
  };

  ball.onclick = () => {
    overlay.style.display = "flex";
    ball.style.display = "none";
    restoreState();
  };


}

export function appendMessagesToCurrentChat(latestMessages) {
  const msgList = document.querySelector("#msgList");
  if (!msgList || !currentSelectedChatId) return;

  const displayedMessageIds = Array.from(msgList.querySelectorAll('[scroll-key]'))
    .map(el => parseInt(el.getAttribute('scroll-key')));

  const newMessages = latestMessages.filter(m => !displayedMessageIds.includes(m.id));

  newMessages.forEach(m => {
    msgList.appendChild(createMessageDOM(m));
  });

  if (newMessages.length > 0) {
    msgList.scrollTop = msgList.scrollHeight;
    console.log(`�� 新追加 ${newMessages.length} 条消息到当前窗口`);
  }
}
export function renderContactSidebar(messageMap = {}) {
  const contactsEl = document.getElementById('lineContacts');
  if (!contactsEl) return;

  contactsEl.innerHTML = '';

  Object.keys(messageMap).forEach(chatId => {
    const chat = messageMap[chatId];
    const wrapper = document.createElement("div");
    wrapper.className = "contact";

    const avatar = document.createElement('img');
    avatar.src = chat?.avatar || 'https://via.placeholder.com/40';
    avatar.title = chat.name;
    avatar.dataset.chatId = chatId;

    const nameSpan = document.createElement('span');
    nameSpan.className = 'name';
    nameSpan.innerText = chat.name;

    const badge = document.createElement("span");
    badge.className = "badge";
    badge.innerText = getUnreadCountFromDOM(chatId);

    avatar.onclick = () => {
      currentSelectedChatId = chatId;
      forceFullRenderMode = false;
      renderMessages(chatId, chat.name, chat.messages || [], document.getElementById('lineMessages'), messageMap);
      highlightCurrentAvatar(contactsEl, chatId);
    };

    wrapper.appendChild(avatar);
    wrapper.appendChild(nameSpan);
    wrapper.appendChild(badge);
    contactsEl.appendChild(wrapper);
  });
    // ✅ 保持当前头像高亮
  highlightCurrentAvatar(contactsEl, currentSelectedChatId);
}


function renderMessages(chatId, chatName, messages = [], msgEl, messageMap) {
  msgEl.innerHTML = `
    <div style="display:flex;justify-content:space-between;align-items:center;">
      <strong>${chatName}</strong>
      <div>
        <button id="fetchHistoryBtn" style="font-size:12px;margin-right:5px;">获取历史记录</button>
        <button id="clearBtn" style="font-size:12px;">清空此人</button>
      </div>
    </div>
    <div id="msgList" style="margin-top:10px;overflow-y:auto;height:calc(100% - 40px);position:relative;"></div>
  `;

  const msgList = msgEl.querySelector("#msgList");
  msgList.innerHTML = '';

  const chatData = messageMap[chatId];
  const sorted = [...messages].sort((a, b) => a.id - b.id);

  if (chatData?.tempMessages?.length) {
    sorted.push(...chatData.tempMessages.sort((a, b) => a.id - b.id));
  }

  renderStartOffset = Math.max(0, sorted.length - PAGE_SIZE);

  function renderPrevPage() {
    const start = Math.max(0, renderStartOffset - PAGE_SIZE);
    const end = renderStartOffset;
    const prevMessages = sorted.slice(start, end);

    const scrollAnchor = msgList.scrollHeight;

    let lastDate = null;
    prevMessages.reverse().forEach(m => {
      const msgDate = m.date || (m.time.split(" ")[0]);

      if (msgDate !== lastDate) {
        msgList.insertBefore(createDateLine(msgDate), msgList.firstChild);
        lastDate = msgDate;
      }

      msgList.insertBefore(createMessageDOM(m), msgList.firstChild);
    });

    msgList.scrollTop = msgList.scrollHeight - scrollAnchor;
    renderStartOffset = start;
  }

  const initialMessages = sorted.slice(renderStartOffset, sorted.length);

  let lastDate = null;
  initialMessages.forEach(m => {
    const msgDate = m.date || (m.time.split(" ")[0]);

    if (msgDate !== lastDate) {
      msgList.appendChild(createDateLine(msgDate));
      lastDate = msgDate;
    }

    msgList.appendChild(createMessageDOM(m));
  });

  msgList.scrollTop = msgList.scrollHeight;

  msgList.onscroll = () => {
    if (msgList.scrollTop <= 5 && renderStartOffset > 0) {
      renderPrevPage();
    }
  };

  document.getElementById("clearBtn").onclick = () => {
    chrome.storage.local.get("messages", res => {
      const store = res.messages || {};
delete store[chatId];
chrome.storage.local.set({ messages: store }, () => {
  currentSelectedChatId = null;
  document.getElementById('lineMessages').innerHTML = '<p>请选择联系人</p>';

  // ✅ 通知内容脚本重建监听器
  window.postMessage({
    source: 'line_chat_monitor',
    action: 'restart_observers'
  }, '*');

  renderUI(store);
});


    });
  };

  document.getElementById("fetchHistoryBtn").onclick = () => {
    forceFullRenderMode = true;
    window.dispatchEvent(new CustomEvent("force-scan-line", { detail: { targetChatId: chatId } }));
  };
}

// ✅ 强制刷新当前联系人消息窗口(分页显示最新20条)
export function renderMessagesForceRefresh(chatId, messages) {
  const msgEl = document.getElementById('lineMessages');
  if (!msgEl) return;

  const chatName = (document.querySelector(`img[data-chat-id="${chatId}"]`)?.title) || '未知';

  const messageMap = {};
  messageMap[chatId] = { messages };

  renderMessages(chatId, chatName, messages, msgEl, messageMap);
}




function createMessageDOM(m) {
  const wrapper = document.createElement("div");
  wrapper.style.display = 'flex';
  wrapper.style.justifyContent = m.from === 'self' ? 'flex-end' : 'flex-start';

  const container = document.createElement("div");
  container.className = "msg-outer-container";
  container.style.textAlign = m.from === 'self' ? 'left' : 'right';  // ✅ 根据方向调整

  // ✅ 已读状态和时间
  const statusRow = document.createElement("div");
  statusRow.className = "msg-status-row";
  statusRow.style.fontSize = "11px";
  statusRow.style.color = "#999";

  if (m.from === 'self' && m.isRead) {
    const readStatus = document.createElement('span');
    readStatus.className = "msg-read-status";
    readStatus.innerText = m.readStatusText || "已讀";
    statusRow.appendChild(readStatus);
    statusRow.appendChild(document.createElement("br"));
  }

  const timeEl = document.createElement("span");
  timeEl.className = "msg-time";
  timeEl.innerText = m.time;
  statusRow.appendChild(timeEl);

  container.appendChild(statusRow);

  const msgClass = m.from === 'self' ? 'msg_rgt msg_wrap' : 'msg_lft msg_wrap';
  const outerDiv = document.createElement("div");
  outerDiv.className = msgClass;
  outerDiv.setAttribute('scroll-key', m.id);

  const bubble = document.createElement("div");
  bubble.className = "msg-block";
  bubble.innerText = m.text;
  bubble.style.background = m.from === 'self' ? '#d0f0c0' : '#f0f0f0';

  outerDiv.appendChild(bubble);

  container.appendChild(outerDiv);

  wrapper.appendChild(container);

  return wrapper;
}

function createDateLine(dateText) {
  const informDate = document.createElement("div");
  informDate.className = "inform_date";
  informDate.style.textAlign = "center";
  informDate.style.margin = "10px 0";

  const span = document.createElement("span");
  span.className = "date";
  span.innerText = dateText;

  informDate.appendChild(span);
  return informDate;
}


function getUnreadCountFromDOM(chatId) {
  const li = document.querySelector(`li[data-key="${chatId}"]`);
  if (!li) return '';
  const span = li.querySelector("span.new");
  return span ? parseInt(span.innerText.trim()) || '' : '';
}

function highlightCurrentAvatar(contactsEl, chatId) {
  document.querySelectorAll('#lineContacts img').forEach(el => el.classList.remove('selected'));
  const avatar = contactsEl.querySelector(`img[data-chat-id="${chatId}"]`);
  if (avatar) avatar.classList.add('selected');
}

function enableDrag(el) {
  const bar = el.querySelector(".title-bar");
  if (!bar) return;
  let isDragging = false;
  let offsetX = 0, offsetY = 0;
  bar.addEventListener("mousedown", (e) => {
    isDragging = true;
    offsetX = e.clientX - el.offsetLeft;
    offsetY = e.clientY - el.offsetTop;
  });
  document.addEventListener("mousemove", (e) => {
    if (isDragging) {
      el.style.left = `${e.clientX - offsetX}px`;
      el.style.top = `${e.clientY - offsetY}px`;
    }
  });
  document.addEventListener("mouseup", () => {
    isDragging = false;
    saveState();
  });
}

function enableSplitter(splitter, leftPane) {
  if (!splitter || !leftPane) return;
  splitter.onmousedown = function (e) {
    e.preventDefault();
    const startX = e.clientX;
    const startWidth = leftPane.offsetWidth;
    function onMouseMove(e) {
      const newWidth = Math.min(300, Math.max(80, startWidth + e.clientX - startX));
      leftPane.style.width = `${newWidth}px`;
    }
    function onMouseUp() {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
      saveState();
    }
    document.addEventListener("mousemove", onMouseMove);
    document.addEventListener("mouseup", onMouseUp);
  };
}

function saveState() {
  const overlay = document.getElementById('lineOverlay');
  if (!overlay) return;
  const state = {
    left: overlay.offsetLeft,
    top: overlay.offsetTop,
    width: overlay.offsetWidth,
    height: overlay.offsetHeight,
    contactsWidth: document.getElementById('lineContacts')?.offsetWidth || 120
  };
  localStorage.setItem('line_chat_state', JSON.stringify(state));
}

function restoreState() {
  const data = localStorage.getItem('line_chat_state');
  if (!data) return;
  const overlay = document.getElementById('lineOverlay');
  if (!overlay) return;
  const { left, top, width, height, contactsWidth } = JSON.parse(data);
  if (left) overlay.style.left = `${left}px`;
  if (top) overlay.style.top = `${top}px`;
  if (width) overlay.style.width = `${width}px`;
  if (height) overlay.style.height = `${height}px`;
  if (contactsWidth) document.getElementById('lineContacts').style.width = `${contactsWidth}px`;
}



// �� 新增:绑定全局函数
window.appendMessagesToCurrentChat = appendMessagesToCurrentChat;
window.renderContactSidebar = renderContactSidebar;
// ✅ 新增:绑定全局函数
window.appendMessagesToCurrentChat = appendMessagesToCurrentChat;
window.renderContactSidebar = renderContactSidebar;
window.renderMessagesForceRefresh = renderMessagesForceRefresh;  // ✅ 新增

window.addEventListener('message', (event) => {
  if (event.data?.source !== 'line_chat_monitor') return;
  if (event.data.action === 'update_chat') {
    const { chatId, messages } = event.data;
    if (chatId === currentSelectedChatId) {
      renderMessagesForceRefresh(chatId, messages);
    }
  }
});