// ==UserScript==
// @name Confluence Plus
// @namespace https://docs.scriptcat.org/
// @version 2.0.1
// @description Enhance your Confluence experience with extra features.
// @author Shu Su
// @homepage https://scriptcat.org/en/script-show-page/6271
// @homepageURL https://scriptcat.org/en/script-show-page/6271
// @match https://*.atlassian.net/wiki/*
// @icon data:image/x-icon;base64,AAABAAEAQEAAAAEAIAAoQAAAFgAAACgAAABAAAAAgAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAADXahn/AAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf//////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ///////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAAAAAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf///////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn//////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ///////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf//////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ////////////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf//////12oZ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////////////////////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf//////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf//////////////////////////////////////////////////////////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/////////////////////////////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn////////////Xahn////////////////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/////////////////////////////////Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ///////Xahn/12oZ/9dqGf///////////////////////////9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/////////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ////////////12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANdqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/9dqGf/Xahn/12oZ/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_info
// @connect *
// @run-at document-idle
// @license MIT
// ==/UserScript==
/* global GM_info */
/* global GM_xmlhttpRequest */
/* global GM_getValue, GM_setValue */
/* global GM_addStyle */
(() => {
// src/plugins/confluence/runtime/utils.js
var Utils = {
extractPageId(url = location.href) {
const source = String(url || "");
const match = source.match(/\/pages\/(\d+)/i) || source.match(/\/page\/(\d+)/i) || source.match(/[?]pageId=(\d+)/i) || source.match(/[?]homepageId=(\d+)/i);
if (match) {
return match[1];
}
try {
const queryPageId = new URL(source, location.href).searchParams.get(
"homepageId"
);
if (/^\d+$/.test(queryPageId || "")) {
return queryPageId;
}
} catch {
}
const meta = document.querySelector(
'meta[name="ajs-page-id"], meta[name="ajs-content-id"]'
);
const dataElement = document.querySelector(
"[data-page-id], [data-content-id]"
);
const fallback = meta?.content || dataElement?.getAttribute("data-page-id") || dataElement?.getAttribute("data-content-id") || "";
return /^\d+$/.test(fallback) ? fallback : "";
},
isValidPageId(pageId) {
return /^\d+$/.test(pageId);
},
isBlogPostUrl(url = location.href) {
try {
const pathname = new URL(url, location.href).pathname;
return /\/spaces\/[^/]+\/blog(?:\/|$)/i.test(pathname);
} catch {
return /\/spaces\/[^/]+\/blog(?:\/|$)/i.test(String(url || ""));
}
},
debounce(fn, delay = 300) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
};
// src/plugins/confluence/runtime/config.js
var Config = {
storageKey: "confluence_plus_state",
api: {
v1Path: "/wiki/rest/api",
v2Path: "/wiki/api/v2"
},
request: { timeout: 15e3 },
ai: {
defaultApiVersion: "2025-03-01-preview",
defaultModelsApiVersion: "2024-10-21"
},
ui: { defaultWidth: 420, defaultHeight: 700, minWidth: 320, minHeight: 400 },
settings: {
defaultLanguage: "system",
defaultTheme: "system",
defaultFloatingButtonRight: 120,
minFloatingButtonRight: 0,
maxFloatingButtonRight: 1e3
}
};
// src/plugins/confluence/runtime/logger.js
var Logger = {
info(event, data) {
if (typeof data === "undefined") {
console.info("[Confluence Plus] " + event);
return;
}
console.info("[Confluence Plus] " + event, data);
},
warn(event, data) {
console.warn("[Confluence Plus] " + event, data);
},
error(event, error) {
console.error("[Confluence Plus] " + event, error);
}
};
// src/plugins/confluence/runtime/storage.js
var Storage = {
loadData() {
try {
const raw = GM_getValue(Config.storageKey, "{}");
const data = JSON.parse(raw);
if (data.auth) {
delete data.auth;
GM_setValue(Config.storageKey, JSON.stringify(data));
}
return data;
} catch (error) {
Logger.error("storage.load-failed", error);
return {};
}
},
loadUi() {
const ui = this.loadData().ui || {};
const result = {};
if (typeof ui.width === "number") result.width = ui.width;
if (typeof ui.height === "number") result.height = ui.height;
return result;
},
saveUi(ui) {
const data = this.loadData();
data.ui = { width: ui.width, height: ui.height };
GM_setValue(Config.storageKey, JSON.stringify(data));
},
loadAi() {
return this.loadData().ai || {};
},
saveAi(ai) {
const data = this.loadData();
data.ai = ai;
GM_setValue(Config.storageKey, JSON.stringify(data));
},
clearAi() {
const data = this.loadData();
delete data.ai;
GM_setValue(Config.storageKey, JSON.stringify(data));
},
loadSettings() {
return this.loadData().settings || {};
},
saveSettings(settings) {
const data = this.loadData();
data.settings = settings;
GM_setValue(Config.storageKey, JSON.stringify(data));
}
};
// src/plugins/confluence/runtime/router.js
function createRouter({ Store: Store2, Logger: Logger2, Utils: Utils2 }) {
let lastUrl = "";
function getContext() {
const url = location.href;
return { url, pageId: Utils2.extractPageId(url) };
}
function emitIfChanged() {
const context = getContext();
if (context.url === lastUrl) return;
lastUrl = context.url;
Store2.resetPanelSession();
Store2.setContext(context);
Logger2.info("route.changed", context);
}
function init() {
const rawPushState = history.pushState;
const rawReplaceState = history.replaceState;
history.pushState = function(...args) {
rawPushState.apply(this, args);
setTimeout(emitIfChanged, 50);
};
history.replaceState = function(...args) {
rawReplaceState.apply(this, args);
setTimeout(emitIfChanged, 50);
};
window.addEventListener("popstate", emitIfChanged);
emitIfChanged();
}
return { init, getContext };
}
// src/plugins/confluence/runtime/http.js
function createHttp({ Config: Config2, getLocaleText: getLocaleText2 }) {
const Http2 = {
requestViaManager(url, options = {}) {
const headers = {
Accept: "application/json",
...options.headers || {}
};
const logRequest = options.logLabel ? {
method: options.method || "GET",
url,
headers: Object.fromEntries(
Object.entries(headers).map(([key, value]) => [
key,
/api-key/i.test(key) ? "[REDACTED]" : value
])
),
body: options.body
} : null;
if (logRequest) {
console.info(`[Confluence Plus] ${options.logLabel} request`, logRequest);
}
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: options.method || "GET",
url,
data: options.body,
headers,
timeout: Config2.request.timeout,
onload(response) {
if (logRequest) {
console.info(`[Confluence Plus] ${options.logLabel} response`, {
status: response.status,
responseText: response.responseText
});
}
if (response.status < 200 || response.status >= 300) {
const detail = (response.responseText || "").replace(/\s+/g, " ").slice(0, 500);
reject(new Error(
`HTTP ${response.status}: ${detail}`
));
return;
}
if (response.status === 204) {
resolve(null);
return;
}
try {
resolve(JSON.parse(response.responseText));
} catch {
reject(new Error(
getLocaleText2("azureNonJsonResponse")
));
}
},
onerror() {
reject(new Error(getLocaleText2("azureCrossOriginRequestFailed")));
},
ontimeout() {
reject(new Error(getLocaleText2("azureApiTimedOut")));
}
});
});
},
async request(url, options = {}) {
if (options.crossOrigin) {
if (typeof GM_xmlhttpRequest !== "function") {
throw new Error(getLocaleText2("gmRequestUnavailable"));
}
return this.requestViaManager(url, options);
}
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, Config2.request.timeout);
try {
const response = await fetch(url, {
...options,
credentials: options.credentials || "same-origin",
signal: controller.signal,
headers: {
Accept: "application/json",
...options.headers || {}
}
});
if (!response.ok) {
const text = await response.text();
const detail = text.replace(/\s+/g, " ").slice(0, 500);
throw new Error(
`HTTP ${response.status}: ${detail}`
);
}
if (response.status === 204) {
return null;
}
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("application/json")) {
throw new Error(
getLocaleText2("confluenceNonJsonResponse")
);
}
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
throw new Error(getLocaleText2("confluenceApiTimedOut"));
}
throw error;
} finally {
clearTimeout(timeoutId);
}
},
get(url, options = {}) {
return this.request(url, {
method: "GET",
...options
});
},
post(url, body, options = {}) {
return this.request(url, {
...options,
method: "POST",
body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
...options.headers || {}
}
});
}
};
return Http2;
}
// src/plugins/confluence/runtime/locale-messages.js
var localeMessages = {
en: {
settings: "Confluence Plus settings",
configure: "Configure Confluence Plus",
close: "Close",
general: "General",
contentModule: "Content",
content: "Content",
language: "Language",
languageDescription: "Choose the plugin display language.",
theme: "Theme",
themeDescription: "Light, dark, or follow system.",
floatingPosition: "Floating button position",
floatingPositionDescription: "Distance from the right edge of the screen.",
system: "System",
english: "English",
chinese: "\u4E2D\u6587",
light: "Light",
dark: "Dark",
pixels: "px",
noSettings: "No settings available yet.",
aiConfiguration: "AI Configuration",
aiConfigurationDescription: "Configure Azure OpenAI for Confluence content features.",
aiStatusNotConfigured: "Not configured",
aiStatusConfigured: "Configured",
aiStatusAvailable: "Configured",
aiStatusUnavailable: "Unavailable",
apiEndpoint: "API endpoint",
apiEndpointDescription: "For example: https://your-resource.openai.azure.com/",
apiKey: "API Key",
apiVersion: "API version",
apiVersionDescription: "Azure OpenAI API version used for chat completions.",
apiKeyPlaceholder: "Enter Azure OpenAI API Key",
show: "Show",
hide: "Hide",
modelDeployment: "Deployment",
modelDeploymentDescription: "Enter the deployment name used by Azure OpenAI.",
modelDeploymentPlaceholder: "Enter a deployment name",
tokenParameter: "Token parameter",
tokenParameterDescription: "Newer reasoning models use max_completion_tokens; older models use max_tokens.",
maxCompletionTokens: "max_completion_tokens",
maxTokens: "max_tokens",
notTested: "Connection has not been tested.",
connecting: "Connecting to Azure OpenAI using the specified deployment\u2026",
testConnection: "Test connection",
testConnectionTooltip: "Test the connection using the specified deployment.",
clear: "Clear",
missingEndpointOrKey: "Endpoint and API Key are required.",
deploymentRequired: "Enter a deployment name first.",
deploymentVerificationFailed: "Deployment verification returned an invalid response.",
deploymentVerified: "Deployment connection succeeded.",
contentToolTitle: "Content Management Tool",
contentToolSubtitle: "Use Tool to manage the content",
spacePageTree: "Space Page Tree",
spacePageTreeDescription: "Browse and expand the complete page tree of the current Space.",
generateLabel: "Generate Label",
generateLabelDescription: "Analyze the current page and suggest labels using the configured AI.",
aiConfigurationRequired: "AI configuration required:",
currentSpaceIcon: "Current Space icon",
leftClick: "Left-click: Open feature window",
rightClick: "Right-click: Open configuration window",
maximizeWindow: "Maximize window",
restoreWindow: "Restore window",
closeWindow: "Close window",
openPage: "Open page",
loadingSpaceTree: "Loading Space page tree\u2026",
reloadSpaceTree: "Reload the Space page tree",
collapseChildren: "Collapse children",
expandChildren: "Expand children",
childPages: "{{count}} child page(s)",
currentPageBadge: "Current",
noPagesInSpace: "No pages found in this Space.",
loadedPages: "Loaded {{count}} page(s)",
loadedPagesFromCache: "Loaded {{count}} page(s) from session cache",
generateSuggestionsForPage: "Generate suggestions for the current page",
pageContentSentToAi: "The page title and content will be sent to the configured Azure OpenAI endpoint.",
generateLabelSuggestions: "Analyze the current page and generate label suggestions",
generateSuggestions: "Generate Suggestions",
recommendedLabelsFor: "Recommended Labels for {{title}}",
recommendedLabels: "Recommended Labels",
addLabelManually: "Add a label manually",
add: "Add",
applySelectedLabels: "Apply Selected Labels",
applySelectedLabelsTitle: "Add the selected labels to the current Confluence page",
removeLabel: "Remove label",
applyLabelsQuestion: "Apply labels?",
applyOneLabelQuestion: "Apply 1 selected label?",
applyManyLabelsQuestion: "Apply the {{count}} selected labels?",
applyLabelsMessage: "Add to the current page: {{labels}}",
cancel: "Cancel",
apply: "Apply",
selectNewValidLabel: "Select at least one new valid label",
noLabelsToApplyTooltip: "Select at least one new valid label to apply",
applyingLabels: "Applying labels\u2026",
addedLabelsRefreshing: "Added {{count}} label(s). Refreshing page\u2026",
loadingAndGenerating: "Loading page content and generating suggestions\u2026",
configureAiBeforeLabels: "Configure AI before generating labels",
generatedSuggestions: "Generated {{count}} suggestion(s)",
noNewLabelSuggestions: "No new label suggestions were generated",
labelsCount: "{{count}} label(s): {{labels}}",
aiConfigurationRequiredError: "Configure and save a valid AI configuration first.",
aiLabelResponseMissing: "AI did not return recognizable Label suggestions.",
aiLabelResponseInvalid: "AI returned invalid JSON for the Label suggestions.",
invalidPageId: "The current page does not have a valid Confluence Page ID.",
missingSpaceId: "The current page does not have a recognizable Space ID.",
missingSpaceInfo: "The current Space does not have a recognizable name or Space Key.",
endpointHttpsRequired: "Azure OpenAI endpoint must use HTTPS.",
unsupportedV1Endpoint: "Azure OpenAI /openai/v1 endpoint is not supported yet; use an Azure resource endpoint.",
endpointMustNotContainCredentials: "Endpoint must not contain credentials, query parameters, or a hash.",
endpointRequired: "Enter an Azure OpenAI endpoint.",
apiKeyRequired: "Enter an Azure OpenAI API key.",
apiVersionRequired: "API version cannot be empty.",
azureNonJsonResponse: "Azure OpenAI returned a non-JSON response.",
azureCrossOriginRequestFailed: "Azure OpenAI cross-origin request failed.",
azureApiTimedOut: "Azure OpenAI API request timed out.",
gmRequestUnavailable: "The UserScript cross-origin request API is unavailable. Grant the script GM_xmlhttpRequest permission.",
confluenceNonJsonResponse: "Confluence returned a non-JSON response; you may need to sign in again.",
confluenceApiTimedOut: "Confluence API request timed out."
},
"zh-CN": {
settings: "Confluence Plus \u8BBE\u7F6E",
configure: "\u914D\u7F6E Confluence Plus",
close: "\u5173\u95ED",
general: "\u5E38\u89C4",
contentModule: "\u5185\u5BB9",
content: "\u5185\u5BB9",
language: "\u8BED\u8A00",
languageDescription: "\u9009\u62E9\u63D2\u4EF6\u663E\u793A\u8BED\u8A00\u3002",
theme: "\u4E3B\u9898",
themeDescription: "\u6D45\u8272\u3001\u6DF1\u8272\u6216\u8DDF\u968F\u7CFB\u7EDF\u3002",
floatingPosition: "\u6D6E\u52A8\u6309\u94AE\u4F4D\u7F6E",
floatingPositionDescription: "\u8DDD\u79BB\u5C4F\u5E55\u53F3\u4FA7\u7684\u8DDD\u79BB\u3002",
system: "\u7CFB\u7EDF",
english: "English",
chinese: "\u4E2D\u6587",
light: "\u6D45\u8272",
dark: "\u6DF1\u8272",
pixels: "px",
noSettings: "\u6682\u65F6\u6CA1\u6709\u53EF\u914D\u7F6E\u9879\u3002",
aiConfiguration: "AI \u914D\u7F6E",
aiConfigurationDescription: "\u914D\u7F6E Azure OpenAI\uFF0C\u4EE5\u4F9B Confluence \u5185\u5BB9\u529F\u80FD\u8C03\u7528\u3002",
aiStatusNotConfigured: "\u672A\u914D\u7F6E",
aiStatusConfigured: "\u5DF2\u914D\u7F6E",
aiStatusAvailable: "\u5DF2\u914D\u7F6E",
aiStatusUnavailable: "\u4E0D\u53EF\u7528",
apiEndpoint: "API \u7AEF\u70B9",
apiEndpointDescription: "\u4F8B\u5982\uFF1Ahttps://your-resource.openai.azure.com/",
apiKey: "API Key",
apiVersion: "API \u7248\u672C",
apiVersionDescription: "\u7528\u4E8E Chat Completions \u7684 Azure OpenAI API \u7248\u672C\u3002",
apiKeyPlaceholder: "\u8F93\u5165 Azure OpenAI API Key",
show: "\u663E\u793A",
hide: "\u9690\u85CF",
modelDeployment: "\u90E8\u7F72",
modelDeploymentDescription: "\u8F93\u5165 Azure OpenAI \u4F7F\u7528\u7684\u90E8\u7F72\u540D\u79F0\u3002",
modelDeploymentPlaceholder: "\u8BF7\u8F93\u5165\u90E8\u7F72\u540D\u79F0",
tokenParameter: "Token \u6570\u91CF\u53C2\u6570",
tokenParameterDescription: "\u8F83\u65B0\u7684\u63A8\u7406\u6A21\u578B\u4F7F\u7528 max_completion_tokens\uFF0C\u65E7\u6A21\u578B\u4F7F\u7528 max_tokens\u3002",
maxCompletionTokens: "max_completion_tokens",
maxTokens: "max_tokens",
notTested: "\u5C1A\u672A\u6D4B\u8BD5\u8FDE\u63A5\u3002",
connecting: "\u6B63\u5728\u4F7F\u7528\u6307\u5B9A\u7684\u90E8\u7F72\u8FDE\u63A5 Azure OpenAI\u2026",
testConnection: "\u6D4B\u8BD5\u8FDE\u63A5",
testConnectionTooltip: "\u4F7F\u7528\u6307\u5B9A\u7684\u90E8\u7F72\u6D4B\u8BD5\u8FDE\u63A5\u3002",
clear: "\u6E05\u9664",
missingEndpointOrKey: "Endpoint \u548C API Key \u4E0D\u80FD\u4E3A\u7A7A\u3002",
deploymentRequired: "\u8BF7\u5148\u8F93\u5165\u90E8\u7F72\u540D\u79F0\u3002",
deploymentVerificationFailed: "\u90E8\u7F72\u9A8C\u8BC1\u8FD4\u56DE\u4E86\u65E0\u6548\u54CD\u5E94\u3002",
deploymentVerified: "\u90E8\u7F72\u8FDE\u63A5\u6210\u529F\u3002",
contentToolTitle: "\u5185\u5BB9\u7BA1\u7406\u5DE5\u5177",
contentToolSubtitle: "\u4F7F\u7528\u5DE5\u5177\u7BA1\u7406\u5185\u5BB9",
spacePageTree: "Space \u9875\u9762\u6811",
spacePageTreeDescription: "\u6D4F\u89C8\u5E76\u5C55\u5F00\u5F53\u524D Space \u7684\u5B8C\u6574\u9875\u9762\u6811\u3002",
generateLabel: "\u751F\u6210 Label",
generateLabelDescription: "\u4F7F\u7528\u5DF2\u914D\u7F6E\u7684 AI \u5206\u6790\u5F53\u524D\u9875\u9762\u5E76\u751F\u6210\u6807\u7B7E\u5EFA\u8BAE\u3002",
aiConfigurationRequired: "\u9700\u8981 AI \u914D\u7F6E\uFF1A",
currentSpaceIcon: "\u5F53\u524D Space \u56FE\u6807",
leftClick: "\u5DE6\u952E\uFF1A\u6253\u5F00\u529F\u80FD\u7A97\u53E3",
rightClick: "\u53F3\u952E\uFF1A\u6253\u5F00\u914D\u7F6E\u7A97\u53E3",
maximizeWindow: "\u6700\u5927\u5316\u7A97\u53E3",
restoreWindow: "\u8FD8\u539F\u7A97\u53E3",
closeWindow: "\u5173\u95ED\u7A97\u53E3",
openPage: "\u6253\u5F00\u9875\u9762",
loadingSpaceTree: "\u6B63\u5728\u52A0\u8F7D Space \u9875\u9762\u6811\u2026",
reloadSpaceTree: "\u91CD\u65B0\u52A0\u8F7D Space \u9875\u9762\u6811",
collapseChildren: "\u6536\u8D77\u5B50\u9875\u9762",
expandChildren: "\u5C55\u5F00\u5B50\u9875\u9762",
childPages: "{{count}} \u4E2A\u5B50\u9875\u9762",
currentPageBadge: "\u5F53\u524D\u9875\u9762",
noPagesInSpace: "\u5F53\u524D Space \u4E2D\u6CA1\u6709\u627E\u5230\u9875\u9762\u3002",
loadedPages: "\u5DF2\u52A0\u8F7D {{count}} \u4E2A\u9875\u9762",
loadedPagesFromCache: "\u5DF2\u4ECE\u4F1A\u8BDD\u7F13\u5B58\u52A0\u8F7D {{count}} \u4E2A\u9875\u9762",
generateSuggestionsForPage: "\u4E3A\u5F53\u524D\u9875\u9762\u751F\u6210\u5EFA\u8BAE",
pageContentSentToAi: "\u9875\u9762\u6807\u9898\u548C\u5185\u5BB9\u5C06\u53D1\u9001\u5230\u5DF2\u914D\u7F6E\u7684 Azure OpenAI \u7AEF\u70B9\u3002",
generateLabelSuggestions: "\u5206\u6790\u5F53\u524D\u9875\u9762\u5E76\u751F\u6210\u6807\u7B7E\u5EFA\u8BAE",
generateSuggestions: "\u751F\u6210\u5EFA\u8BAE",
recommendedLabelsFor: "{{title}} \u7684\u63A8\u8350\u6807\u7B7E",
recommendedLabels: "\u63A8\u8350\u6807\u7B7E",
addLabelManually: "\u624B\u52A8\u6DFB\u52A0\u6807\u7B7E",
add: "\u6DFB\u52A0",
applySelectedLabels: "\u5E94\u7528\u9009\u4E2D\u7684\u6807\u7B7E",
applySelectedLabelsTitle: "\u5C06\u9009\u4E2D\u7684\u6807\u7B7E\u6DFB\u52A0\u5230\u5F53\u524D Confluence \u9875\u9762",
removeLabel: "\u79FB\u9664\u6807\u7B7E",
applyLabelsQuestion: "\u5E94\u7528\u6807\u7B7E\uFF1F",
applyOneLabelQuestion: "\u5E94\u7528\u9009\u4E2D\u7684 1 \u4E2A\u6807\u7B7E\uFF1F",
applyManyLabelsQuestion: "\u5E94\u7528\u9009\u4E2D\u7684 {{count}} \u4E2A\u6807\u7B7E\uFF1F",
applyLabelsMessage: "\u5C06\u4EE5\u4E0B\u6807\u7B7E\u6DFB\u52A0\u5230\u5F53\u524D\u9875\u9762\uFF1A{{labels}}",
cancel: "\u53D6\u6D88",
apply: "\u5E94\u7528",
selectNewValidLabel: "\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u6709\u6548\u7684\u65B0\u6807\u7B7E",
noLabelsToApplyTooltip: "\u8BF7\u81F3\u5C11\u9009\u62E9\u4E00\u4E2A\u6709\u6548\u7684\u65B0\u6807\u7B7E",
applyingLabels: "\u6B63\u5728\u5E94\u7528\u6807\u7B7E\u2026",
addedLabelsRefreshing: "\u5DF2\u6DFB\u52A0 {{count}} \u4E2A\u6807\u7B7E\uFF0C\u6B63\u5728\u5237\u65B0\u9875\u9762\u2026",
loadingAndGenerating: "\u6B63\u5728\u52A0\u8F7D\u9875\u9762\u5185\u5BB9\u5E76\u751F\u6210\u5EFA\u8BAE\u2026",
configureAiBeforeLabels: "\u751F\u6210\u6807\u7B7E\u524D\u8BF7\u5148\u914D\u7F6E AI",
generatedSuggestions: "\u5DF2\u751F\u6210 {{count}} \u6761\u5EFA\u8BAE",
noNewLabelSuggestions: "\u672A\u751F\u6210\u65B0\u7684\u6807\u7B7E\u5EFA\u8BAE",
labelsCount: "{{count}} \u4E2A Label\uFF1A{{labels}}",
aiConfigurationRequiredError: "\u8BF7\u5148\u914D\u7F6E\u5E76\u4FDD\u5B58\u53EF\u7528\u7684 AI \u914D\u7F6E\u3002",
aiLabelResponseMissing: "AI \u672A\u8FD4\u56DE\u53EF\u8BC6\u522B\u7684 Label \u5EFA\u8BAE\u3002",
aiLabelResponseInvalid: "AI \u8FD4\u56DE\u7684 Label \u5EFA\u8BAE\u4E0D\u662F\u6709\u6548 JSON\u3002",
invalidPageId: "\u5F53\u524D\u9875\u9762\u6CA1\u6709\u6709\u6548\u7684 Confluence Page ID\u3002",
missingSpaceId: "\u5F53\u524D\u9875\u9762\u6CA1\u6709\u53EF\u8BC6\u522B\u7684 Space ID\u3002",
missingSpaceInfo: "\u5F53\u524D Space \u6CA1\u6709\u53EF\u8BC6\u522B\u7684\u540D\u79F0\u6216 Space Key\u3002",
endpointHttpsRequired: "Azure OpenAI \u7AEF\u70B9\u5FC5\u987B\u4F7F\u7528 HTTPS\u3002",
unsupportedV1Endpoint: "\u6682\u4E0D\u652F\u6301 Azure OpenAI /openai/v1 \u7AEF\u70B9\uFF0C\u8BF7\u4F7F\u7528 Azure resource endpoint\u3002",
endpointMustNotContainCredentials: "Endpoint \u4E0D\u5E94\u5305\u542B\u8D26\u53F7\u3001\u67E5\u8BE2\u53C2\u6570\u6216 hash\u3002",
endpointRequired: "\u8BF7\u8F93\u5165 Azure OpenAI Endpoint\u3002",
apiKeyRequired: "\u8BF7\u8F93\u5165 Azure OpenAI API Key\u3002",
apiVersionRequired: "API Version \u4E0D\u80FD\u4E3A\u7A7A\u3002",
azureNonJsonResponse: "Azure OpenAI \u8FD4\u56DE\u4E86\u975E JSON \u54CD\u5E94\u3002",
azureCrossOriginRequestFailed: "Azure OpenAI \u8DE8\u57DF\u8BF7\u6C42\u5931\u8D25\u3002",
azureApiTimedOut: "Azure OpenAI API \u8BF7\u6C42\u8D85\u65F6\u3002",
gmRequestUnavailable: "UserScript \u8DE8\u57DF\u8BF7\u6C42\u63A5\u53E3\u4E0D\u53EF\u7528\uFF0C\u8BF7\u786E\u8BA4\u811A\u672C\u5DF2\u6388\u4E88 GM_xmlhttpRequest \u6743\u9650\u3002",
confluenceNonJsonResponse: "Confluence \u8FD4\u56DE\u4E86\u975E JSON \u54CD\u5E94\uFF0C\u53EF\u80FD\u9700\u8981\u91CD\u65B0\u767B\u5F55\u3002",
confluenceApiTimedOut: "Confluence API \u8BF7\u6C42\u8D85\u65F6\u3002"
}
};
// src/shared/locale.js
var VALID_UI_LANGUAGES = ["system", "en", "zh-CN"];
function resolveUiLanguage(value, browserLanguage = navigator.language || "") {
return value === "zh-CN" || value === "system" && /^zh(?:-|$)/i.test(browserLanguage) ? "zh-CN" : "en";
}
function interpolateLocaleText(text, key, variables = {}) {
return (text || key).replace(/\{\{(\w+)\}\}/g, (_, name) => variables[name] ?? "");
}
// src/shared/runtime/locale-runtime.js
function lookupMessage(messages, language, key) {
const languageMessages = messages?.[language];
if (languageMessages && Object.prototype.hasOwnProperty.call(languageMessages, key)) {
return languageMessages[key];
}
const parts = key.split(".");
let entry = messages;
parts.forEach((part) => {
entry = entry?.[part];
});
return entry?.[language];
}
function createLocaleRuntime({ messages, getLanguage, browserLanguage }) {
const resolveLanguage = (value = getLanguage()) => resolveUiLanguage(value, browserLanguage?.() ?? navigator.language ?? "");
const getLocaleText2 = (key, value = getLanguage(), variables = {}) => interpolateLocaleText(lookupMessage(messages, resolveLanguage(value), key), key, variables);
return { validUiLanguages: VALID_UI_LANGUAGES, resolveUiLanguage: resolveLanguage, getLocaleText: getLocaleText2 };
}
// src/plugins/confluence/runtime/locale.js
function createLocaleService({ getLanguage, localeMessages: localeMessages2 }) {
const { validUiLanguages, resolveUiLanguage: resolveUiLanguage2, getLocaleText: getLocaleText2 } = createLocaleRuntime({ messages: localeMessages2, getLanguage });
return { validUiLanguages, resolveUiLanguage: resolveUiLanguage2, getLocaleText: getLocaleText2 };
}
// src/plugins/confluence/runtime/confluence-api.js
function createConfluenceApi({ Config: Config2, Http: Http2, origin = location.origin }) {
const api = {
getV1Url(path) {
return origin + Config2.api.v1Path + path;
},
getV2Url(path) {
return origin + Config2.api.v2Path + path;
},
folders: {
get(folderId) {
return Http2.get(api.getV2Url("/folders/" + encodeURIComponent(folderId)));
}
},
spaces: {
get(spaceId) {
return Http2.get(api.getV2Url("/spaces/" + encodeURIComponent(spaceId)));
},
async getByKey(spaceKey) {
const query = new URLSearchParams({ keys: spaceKey, limit: "1" });
const data = await Http2.get(api.getV2Url("/spaces") + "?" + query);
return Array.isArray(data.results) ? data.results[0] : null;
}
},
pages: {
get(pageId) {
return Http2.get(api.getV2Url("/pages/" + pageId));
},
getForAi(pageId) {
const query = new URLSearchParams({ expand: "body.storage,metadata.labels" });
return Http2.get(api.getV1Url("/content/" + pageId) + "?" + query);
},
addLabels(pageId, labels) {
return Http2.post(api.getV1Url("/content/" + pageId + "/label"), labels.map((name) => ({ prefix: "global", name })));
},
getChildren(pageId) {
return Http2.get(api.getV2Url("/pages/" + pageId + "/children"));
},
getSpacePages(spaceId, cursor = "") {
const query = new URLSearchParams({ limit: "250" });
if (cursor) query.set("cursor", cursor);
return Http2.get(api.getV2Url("/spaces/" + encodeURIComponent(spaceId) + "/pages") + "?" + query);
}
},
pageStates: {
get(pageId) {
return Http2.get(api.getV1Url("/content/" + pageId + "/state"));
}
}
};
return api;
}
// src/plugins/confluence/runtime/ai-config.js
function createAIConfig({ Config: Config2, Storage: Storage2, getLocaleText: getLocaleText2 }) {
const AIConfig2 = (() => {
const defaults = {
schemaVersion: 2,
endpoint: "",
apiKey: "",
apiVersion: Config2.ai.defaultApiVersion,
modelsApiVersion: Config2.ai.defaultModelsApiVersion,
deployment: "",
deployments: [],
tokenParameter: "max_completion_tokens",
lastCheck: null
};
const stored = Storage2.loadAi();
let config = stored?.schemaVersion === defaults.schemaVersion ? {
...defaults,
...stored,
deployments: Array.isArray(stored.deployments) ? [...stored.deployments] : []
} : JSON.parse(JSON.stringify(defaults));
if (stored?.schemaVersion !== defaults.schemaVersion) {
Storage2.saveAi(config);
}
function normalizeEndpoint(endpoint) {
const value = endpoint.trim().replace(/\/+$/, "");
const url = new URL(value);
if (url.protocol !== "https:") {
throw new Error(getLocaleText2("endpointHttpsRequired"));
}
if (/\/openai\/v1\/?$/i.test(url.pathname)) {
throw new Error(getLocaleText2("unsupportedV1Endpoint"));
}
if (url.username || url.password || url.search || url.hash) {
throw new Error(getLocaleText2("endpointMustNotContainCredentials"));
}
return url.origin + url.pathname.replace(/\/+$/, "");
}
function validate(candidate) {
const next = {
...config,
...candidate
};
if (!next.endpoint) {
throw new Error(getLocaleText2("endpointRequired"));
}
if (!next.apiKey) {
throw new Error(getLocaleText2("apiKeyRequired"));
}
next.endpoint = normalizeEndpoint(next.endpoint);
next.apiVersion = next.apiVersion.trim();
next.modelsApiVersion = next.modelsApiVersion.trim();
next.deployment = next.deployment.trim();
next.tokenParameter = ["max_tokens", "max_completion_tokens"].includes(next.tokenParameter) ? next.tokenParameter : defaults.tokenParameter;
if (!next.apiVersion || !next.modelsApiVersion) {
throw new Error(getLocaleText2("apiVersionRequired"));
}
return next;
}
return {
get() {
return { ...config, deployments: [...config.deployments] };
},
validate,
save(candidate) {
const previous = config;
const next = validate(candidate);
const changed = previous.endpoint !== next.endpoint || previous.apiKey !== next.apiKey || previous.apiVersion !== next.apiVersion || previous.deployment !== next.deployment || previous.tokenParameter !== next.tokenParameter;
const hasExplicitHealth = Object.prototype.hasOwnProperty.call(
candidate,
"lastCheck"
);
config = {
...next,
lastCheck: hasExplicitHealth ? next.lastCheck : changed ? null : next.lastCheck || null
};
Storage2.saveAi(config);
return this.get();
},
updateDraft(candidate = {}) {
const next = {
...config,
...candidate,
endpoint: String(candidate.endpoint ?? config.endpoint ?? "").trim(),
apiKey: String(candidate.apiKey ?? config.apiKey ?? "").trim(),
apiVersion: String(candidate.apiVersion ?? config.apiVersion ?? defaults.apiVersion).trim(),
modelsApiVersion: String(candidate.modelsApiVersion ?? config.modelsApiVersion ?? defaults.modelsApiVersion).trim(),
deployment: String(candidate.deployment ?? config.deployment ?? "").trim(),
tokenParameter: ["max_tokens", "max_completion_tokens"].includes(candidate.tokenParameter) ? candidate.tokenParameter : config.tokenParameter || defaults.tokenParameter,
deployments: Array.isArray(candidate.deployments) ? [...candidate.deployments] : [...config.deployments]
};
if (next.endpoint) {
try {
next.endpoint = normalizeEndpoint(next.endpoint);
} catch {
next.endpoint = "";
}
}
const healthChanged = next.endpoint !== config.endpoint || next.apiKey !== config.apiKey || next.apiVersion !== config.apiVersion || next.deployment !== config.deployment || next.tokenParameter !== config.tokenParameter;
config = {
...next,
lastCheck: healthChanged ? null : next.lastCheck || null
};
Storage2.saveAi(config);
return this.get();
},
recordHealth(ok, message, metadata = {}) {
config = {
...config,
lastCheck: {
ok,
message,
endpoint: config.endpoint,
deployment: config.deployment,
apiVersion: config.apiVersion,
tokenParameter: config.tokenParameter,
checkedAt: Date.now(),
...metadata
}
};
Storage2.saveAi(config);
},
getStatus() {
if (!config.endpoint || !config.apiKey || !config.deployment) {
return {
key: "not-configured",
label: getLocaleText2("aiStatusNotConfigured")
};
}
if (!config.lastCheck || config.lastCheck.endpoint !== config.endpoint || config.lastCheck.deployment !== config.deployment || config.lastCheck.apiVersion !== config.apiVersion || config.lastCheck.tokenParameter !== config.tokenParameter) {
return {
key: "unverified",
label: getLocaleText2("aiStatusConfigured")
};
}
return config.lastCheck.ok ? {
key: "available",
label: getLocaleText2("aiStatusConfigured")
} : {
key: "unavailable",
label: getLocaleText2("aiStatusUnavailable")
};
},
clear() {
config = { ...defaults };
Storage2.clearAi();
},
isConfigured() {
return Boolean(config.endpoint && config.apiKey);
}
};
})();
return AIConfig2;
}
// src/plugins/confluence/runtime/store.js
function createStore({ Config: Config2, Storage: Storage2, Utils: Utils2 }) {
const Store2 = (() => {
const state = {
context: {
pageId: "",
url: location.href
},
ui: {
x: 120,
y: 120,
width: Config2.ui.defaultWidth,
height: Config2.ui.defaultHeight,
minimized: true,
maximized: false
}
};
const listeners = [];
const persistUi = Utils2.debounce(() => {
Storage2.saveUi(state.ui);
});
function notify(type, value) {
listeners.forEach((listener) => {
listener(type, value, state);
});
}
return {
init() {
Object.assign(state.ui, Storage2.loadUi());
},
getContext() {
return state.context;
},
getUi() {
return state.ui;
},
setContext(context) {
state.context = {
...state.context,
...context
};
notify("context", state.context);
},
updateUi(patch) {
Object.assign(state.ui, patch);
persistUi();
notify("ui", state.ui);
},
resetPanelSession() {
Object.assign(state.ui, {
x: 120,
y: 120,
minimized: true,
maximized: false
});
notify("ui", state.ui);
},
subscribe(listener) {
listeners.push(listener);
return () => {
const index = listeners.indexOf(listener);
if (index >= 0) {
listeners.splice(index, 1);
}
};
}
};
})();
return Store2;
}
// src/plugins/confluence/runtime/azure-openai.js
function createAzureOpenAI({ Http: Http2, AIConfig: AIConfig2, getLocaleText: getLocaleText2 }) {
const AzureOpenAI2 = {
isV1Endpoint(endpoint) {
return /\/openai\/v1$/i.test(endpoint);
},
getUrl(endpoint, path, apiVersion) {
if (this.isV1Endpoint(endpoint)) {
return `${endpoint}${path}`;
}
const query = new URLSearchParams({
"api-version": apiVersion
});
return `${endpoint}${path}?${query}`;
},
getHeaders(apiKey) {
return {
"api-key": apiKey,
Accept: "application/json"
};
},
async verifyDeployment(config) {
const response = await Http2.post(
this.getChatCompletionsUrl(config),
{
messages: [
{
role: "user",
content: 'Return JSON exactly {"ok":true}.'
}
],
response_format: { type: "json_object" },
[config.tokenParameter]: 16
},
{
crossOrigin: true,
credentials: "omit",
logLabel: "AI Test Connection",
headers: this.getHeaders(config.apiKey)
}
);
if (!response || typeof response !== "object") {
throw new Error(getLocaleText2("deploymentVerificationFailed"));
}
return response;
},
async testConnection(candidate) {
const config = AIConfig2.validate(candidate);
if (!config.deployment) {
throw new Error(getLocaleText2("deploymentRequired"));
}
await this.verifyDeployment(config);
return {
deployments: [{ id: config.deployment, label: config.deployment }],
deploymentCount: 1,
message: getLocaleText2("deploymentVerified")
};
},
getChatCompletionsUrl(config) {
const path = this.isV1Endpoint(config.endpoint) ? "/chat/completions" : `/openai/deployments/${encodeURIComponent(config.deployment)}/chat/completions`;
return this.getUrl(config.endpoint, path, config.apiVersion);
},
getResponsesUrl(config) {
const path = this.isV1Endpoint(config.endpoint) ? "/responses" : `/openai/deployments/${encodeURIComponent(config.deployment)}/responses`;
return this.getUrl(config.endpoint, path, config.apiVersion);
},
extractChatOutputText(response) {
return response?.choices?.[0]?.message?.content || "";
},
extractOutputText(response) {
if (typeof response.output_text === "string") {
return response.output_text;
}
const messages = response.output || [];
const texts = [];
messages.forEach((item) => {
(item.content || []).forEach((content) => {
if (content.type === "output_text" && content.text) {
texts.push(content.text);
}
});
});
return texts.join("\n");
},
async suggestLabels({ title, content, existingLabels }) {
const config = AIConfig2.get();
if (!AIConfig2.isConfigured() || !config.deployment) {
throw new Error(getLocaleText2("aiConfigurationRequiredError"));
}
const prompt = [
"Recommend 3 to 10 concise Confluence labels for this page.",
'Return JSON only in the shape {"labels":["label-a"]}.',
"Use lowercase letters, numbers, and hyphens only.",
"Do not include existing labels or explanations.",
`Existing labels: ${(existingLabels || []).join(", ") || "(none)"}`,
`Title: ${title || "(untitled)"}`,
"Page content:",
content.slice(0, 24e3)
].join("\n\n");
const response = await Http2.post(
this.getChatCompletionsUrl(config),
{
messages: [
{
role: "user",
content: prompt
}
],
response_format: { type: "json_object" },
[config.tokenParameter]: 6e3
},
{
crossOrigin: true,
credentials: "omit",
headers: {
...this.getHeaders(config.apiKey)
}
}
);
const output = this.extractChatOutputText(response);
const match = output.match(/\{[\s\S]*\}/);
if (!match) {
throw new Error(getLocaleText2("aiLabelResponseMissing"));
}
let parsed;
try {
parsed = JSON.parse(match[0]);
} catch {
throw new Error(getLocaleText2("aiLabelResponseInvalid"));
}
return Array.isArray(parsed.labels) ? parsed.labels : [];
}
};
return AzureOpenAI2;
}
// src/plugins/confluence/runtime/ai-label-service.js
function createAILabelService({ ConfluenceApi: ConfluenceApi2, AzureOpenAI: AzureOpenAI2 }) {
const AILabelService2 = {
normalize(label) {
return String(label || "").trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
},
getExistingLabels(page) {
return (page.metadata?.labels?.results || []).map((item) => item.name).filter(Boolean);
},
async generate(pageId) {
const page = await ConfluenceApi2.pages.getForAi(pageId);
const existingLabels = this.getExistingLabels(page);
const body = page.body?.storage?.value || "";
const suggestions = await AzureOpenAI2.suggestLabels({
title: page.title,
content: body,
existingLabels
});
const existing = new Set(
existingLabels.map((label) => this.normalize(label))
);
const unique = [];
suggestions.forEach((label) => {
const normalized = this.normalize(label);
if (normalized && !existing.has(normalized) && !unique.includes(normalized)) {
unique.push(normalized);
}
});
return {
page,
existingLabels,
suggestions: unique.slice(0, 10)
};
}
};
return AILabelService2;
}
// src/plugins/confluence/runtime/space-tree-service.js
function createSpaceTreeService({ ConfluenceApi: ConfluenceApi2, Utils: Utils2, Logger: Logger2 }) {
const SpaceTreeService2 = {
getNextCursor(data) {
const directCursor = data.meta?.cursor || data.cursor || "";
if (directCursor) {
return directCursor;
}
const next = data._links?.next || data.links?.next;
if (!next) {
return "";
}
try {
return new URL(next, location.origin).searchParams.get("cursor") || "";
} catch {
return "";
}
},
loadFromSidebar(currentPageId) {
const containers = [
...document.querySelectorAll(
'[role="tree"], nav, aside, [data-testid*="sidebar" i], [data-testid*="tree" i]'
)
];
let best = null;
let bestScore = 0;
containers.forEach((container) => {
const links = [...container.querySelectorAll(
'a[href*="/pages/"]'
)];
const pageLinks = links.filter((link) => {
const id = Utils2.extractPageId(link.href);
return id && link.getClientRects().length;
});
const containsCurrent = pageLinks.some(
(link) => Utils2.extractPageId(link.href) === String(currentPageId)
);
const score = pageLinks.length + (containsCurrent ? 1e3 : 0);
if (score > bestScore) {
best = {
container,
links: pageLinks
};
bestScore = score;
}
});
if (!best || !best.links.length) {
return [];
}
const items = [];
const seen = /* @__PURE__ */ new Set();
const stack = [];
best.links.forEach((link) => {
const id = Utils2.extractPageId(link.href);
if (!id || seen.has(id)) return;
seen.add(id);
const treeItem = link.closest('[role="treeitem"]');
const ariaLevel = treeItem?.getAttribute("aria-level");
let depth = ariaLevel ? Number(ariaLevel) - 1 : NaN;
if (!Number.isFinite(depth)) {
depth = 0;
let listItem = link.closest("li");
while (listItem && best.container.contains(listItem)) {
depth += 1;
listItem = listItem.parentElement?.closest("li");
}
depth = Math.max(0, depth - 1);
}
depth = Math.max(0, depth);
stack.length = Math.min(stack.length, depth);
const title = (link.getAttribute("data-item-title") || link.getAttribute("aria-label") || link.textContent || "(Untitled)").trim();
const parent = depth > 0 ? stack[depth - 1] : null;
const item = {
id,
title,
parentId: parent ? parent.id : null,
sidebarDepth: depth
};
items.push(item);
stack[depth] = item;
stack.length = depth + 1;
});
return items;
},
async load(spaceId) {
const pages = [];
let cursor = "";
do {
const data = await ConfluenceApi2.pages.getSpacePages(
spaceId,
cursor
);
const results = Array.isArray(data.results) ? data.results : [];
pages.push(...results);
cursor = this.getNextCursor(data);
} while (cursor);
const folders = /* @__PURE__ */ new Map();
const pendingFolderIds = new Set(
pages.filter((page) => page.parentType === "folder" && page.parentId).map((page) => String(page.parentId))
);
while (pendingFolderIds.size) {
const folderId = pendingFolderIds.values().next().value;
pendingFolderIds.delete(folderId);
if (folders.has(folderId)) continue;
try {
const folder = await ConfluenceApi2.folders.get(folderId);
if (!folder?.id) continue;
const normalizedFolder = {
...folder,
type: "folder",
id: String(folder.id),
parentId: folder.parentId || null,
parentType: folder.parentType || "page"
};
folders.set(normalizedFolder.id, normalizedFolder);
if (normalizedFolder.parentType === "folder" && normalizedFolder.parentId) {
pendingFolderIds.add(String(normalizedFolder.parentId));
}
} catch (error) {
Logger2.warn("space-tree.folder-load-failed", {
folderId,
error
});
}
}
return [...pages, ...folders.values()];
},
buildTree(pages, currentPageId, spaceRootTitle) {
const normalizedSpaceName = (spaceRootTitle || "").trim();
const spaceRoot = pages.find((page) => (page.title || "").trim() === normalizedSpaceName);
const allowedIds = /* @__PURE__ */ new Set();
if (spaceRoot) {
allowedIds.add(String(spaceRoot.id));
let changed = true;
while (changed) {
changed = false;
pages.forEach((page) => {
const parentId = page.parentId || page.parent?.id;
if (parentId && allowedIds.has(String(parentId)) && !allowedIds.has(String(page.id))) {
allowedIds.add(String(page.id));
changed = true;
}
});
}
}
const nodes = /* @__PURE__ */ new Map();
pages.forEach((page, index) => {
const id = String(page.id);
if (!allowedIds.has(id)) return;
const parentId = page.id === spaceRoot?.id ? null : page.parentId || page.parent?.id || null;
const rawPosition = page.position ?? page.positionInParent ?? page.order ?? page.ordinal ?? page.sortOrder;
const position = Number(rawPosition);
nodes.set(id, {
id,
title: page.title || "(Untitled)",
parentId: parentId ? String(parentId) : null,
children: [],
page,
position: Number.isFinite(position) ? position : null,
sourceIndex: index
});
});
const roots = [];
nodes.forEach((node) => {
const parent = node.parentId ? nodes.get(node.parentId) : null;
if (parent && parent.id !== node.id) {
parent.children.push(node);
} else {
roots.push(node);
}
});
const sortNodes = (items) => {
items.sort((a, b) => {
if (a.position !== null && b.position !== null) {
return a.position - b.position || a.sourceIndex - b.sourceIndex;
}
if (a.position !== null) return -1;
if (b.position !== null) return 1;
return a.sourceIndex - b.sourceIndex;
});
items.forEach((item) => sortNodes(item.children));
};
sortNodes(roots);
const expanded = /* @__PURE__ */ new Set();
let current = nodes.get(String(currentPageId));
while (current) {
expanded.add(current.id);
current = current.parentId ? nodes.get(current.parentId) : null;
}
return {
roots,
nodes,
expanded,
filteredCount: nodes.size
};
}
};
return SpaceTreeService2;
}
// src/plugins/confluence/runtime/content-session-cache.js
function createContentSessionCache({ Logger: Logger2 }) {
const ContentSessionCache2 = (() => {
const prefix = `confluence-plus:session:v1:${location.origin}:`;
function read(name) {
try {
const value = sessionStorage.getItem(`${prefix}${name}`);
return value ? JSON.parse(value) : null;
} catch (error) {
Logger2.warn("session-cache.read-failed", { name, error });
return null;
}
}
function write(name, value) {
try {
sessionStorage.setItem(`${prefix}${name}`, JSON.stringify(value));
} catch (error) {
Logger2.warn("session-cache.write-failed", { name, error });
}
}
function remove(name) {
try {
sessionStorage.removeItem(`${prefix}${name}`);
} catch (error) {
Logger2.warn("session-cache.remove-failed", { name, error });
}
}
function scopedName(scope, id) {
return `${scope}:${encodeURIComponent(String(id))}`;
}
return {
getSpaceTree(spaceId) {
return read(scopedName("space-tree", spaceId));
},
saveSpaceTree(spaceId, value) {
write(scopedName("space-tree", spaceId), value);
},
clearSpaceTree(spaceId) {
remove(scopedName("space-tree", spaceId));
},
getLabels(pageId) {
return read(scopedName("labels", pageId));
},
saveLabels(pageId, value) {
write(scopedName("labels", pageId), value);
},
clearLabels(pageId) {
remove(scopedName("labels", pageId));
}
};
})();
return ContentSessionCache2;
}
// src/shared/theme.js
var VALID_THEMES = ["light", "dark", "system"];
function resolveTheme(value, prefersDark, fallback = "system") {
const theme = VALID_THEMES.includes(value) ? value : fallback;
return theme === "system" ? prefersDark ? "dark" : "light" : theme;
}
function subscribeThemeChange(mediaQuery, handler) {
if (!mediaQuery) return () => {
};
if (typeof mediaQuery.addEventListener === "function") {
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}
if (typeof mediaQuery.addListener === "function") {
mediaQuery.addListener(handler);
return () => mediaQuery.removeListener(handler);
}
return () => {
};
}
// src/shared/toast.js
function createToastController({ state, selector, className, getLocaleText: getLocaleText2, closeLabelKey, getThemeClass, timerKey = "toastTimer", maxVisible = 3 }) {
const ensureStack = () => {
let stack = $(selector);
if (!stack.length) stack = $(`[data-toast-stack].${className}-stack`);
if (stack.length) return stack;
stack = $(`
`).appendTo(document.body);
return stack;
};
const hideItem = (toast) => {
toast.removeClass("is-visible is-success is-info is-error").addClass("is-leaving");
window.setTimeout(() => {
toast.remove();
const stack = toast.parent();
if (!stack.children("[data-toast-item]").length) stack.prop("hidden", true);
}, 180);
};
const controller = {
hide() {
clearTimeout(state[timerKey]);
state[timerKey] = -1;
const stack = $(selector);
stack.children("[data-toast-item]").remove();
stack.prop("hidden", true);
},
show({ level = "info", title = "", message = "", duration = 5e3 } = {}) {
const stack = ensureStack();
stack.toggleClass("is-dark", Boolean(getThemeClass?.()));
const normalized = ["success", "info", "error"].includes(level) ? level : "info";
const toast = $(``);
toast.addClass(`is-${normalized}`).find("[data-toast-title]").text(title || "");
toast.find("[data-toast-message]").text(message || "");
toast.find("[data-toast-time]").text((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }));
toast.find("[data-toast-icon]").text(normalized === "success" ? "\u2713" : normalized === "error" ? "!" : "i");
toast.find("[data-toast-close]").attr("aria-label", getLocaleText2(closeLabelKey));
toast.on("click.toast", "[data-toast-close]", () => hideItem(toast));
stack.prop("hidden", false).append(toast);
const items = stack.children("[data-toast-item]");
if (items.length > maxVisible) items.slice(0, items.length - maxVisible).remove();
requestAnimationFrame(() => toast.addClass("is-visible"));
clearTimeout(state[timerKey]);
state[timerKey] = window.setTimeout(() => hideItem(toast), Math.max(1500, Number(duration) || 5e3));
}
};
return controller;
}
// src/shared/feedback.js
function createPopconfirmController({ getThemeClass, className = "shared-popconfirm" } = {}) {
let active = null;
const hide = () => active?.finish(false);
const show = ({ anchor, title = "", message = "", confirmText = "Confirm", cancelText = "Cancel" } = {}) => new Promise((resolve) => {
hide();
const trigger = anchor?.jquery ? anchor[0] : anchor;
const rect = trigger?.getBoundingClientRect?.();
if (!rect) {
resolve(false);
return;
}
const wrapper = document.createElement("div");
wrapper.className = `${className}-wrap${getThemeClass?.() ? " is-dark" : ""}`;
wrapper.innerHTML = ``;
const popup = wrapper.firstElementChild;
popup.querySelector(`.${className}-title`).textContent = title;
const messageElement = popup.querySelector(`.${className}-message`);
messageElement.textContent = message;
messageElement.style.display = message ? "" : "none";
popup.querySelector(`.${className}-cancel`).textContent = cancelText;
popup.querySelector(`.${className}-confirm`).textContent = confirmText;
document.body.appendChild(wrapper);
const popupRect = popup.getBoundingClientRect();
const left = Math.max(8, Math.min(rect.left + (rect.width - popupRect.width) / 2, window.innerWidth - popupRect.width - 8));
const showAbove = rect.top >= popupRect.height + 16;
const top = showAbove ? rect.top - popupRect.height - 8 : Math.min(window.innerHeight - popupRect.height - 8, rect.bottom + 8);
popup.classList.toggle("is-above", showAbove);
popup.classList.toggle("is-below", !showAbove);
wrapper.style.left = `${left}px`;
wrapper.style.top = `${top}px`;
let finished = false;
const finish = (value) => {
if (finished) return;
finished = true;
document.removeEventListener("keydown", onKeydown, true);
document.removeEventListener("click", onOutside, true);
wrapper.remove();
if (active?.wrapper === wrapper) active = null;
resolve(value);
};
const onKeydown = (event) => {
if (event.key === "Escape") finish(false);
else if (event.key === "Enter") finish(true);
};
const onOutside = (event) => {
if (!wrapper.contains(event.target)) finish(false);
};
active = { finish, wrapper };
popup.querySelector(`.${className}-cancel`).addEventListener("click", () => finish(false));
popup.querySelector(`.${className}-confirm`).addEventListener("click", () => finish(true));
document.addEventListener("keydown", onKeydown, true);
setTimeout(() => {
if (!finished) document.addEventListener("click", onOutside, true);
}, 0);
popup.querySelector(`.${className}-confirm`).focus();
});
return { show, hide };
}
// src/shared/tooltip.js
var PLACEMENTS = ["right", "bottom", "left", "top"];
function positionTooltip(host, {
tooltipSelector = '[role="tooltip"]',
placementClassPrefix,
gap = 10,
fallbackWidth = 280,
fallbackHeight = 48
} = {}) {
const tooltip = host?.querySelector?.(tooltipSelector);
if (!host || !tooltip || !placementClassPrefix) return null;
const view = host.ownerDocument?.defaultView || globalThis.window;
if (!view) return null;
const hostRect = host.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
const width = tooltipRect.width || fallbackWidth;
const height = tooltipRect.height || fallbackHeight;
const viewportPadding = 8;
const spaces = { right: view.innerWidth - hostRect.right, bottom: view.innerHeight - hostRect.bottom, left: hostRect.left, top: hostRect.top };
const fits = {
right: spaces.right >= width + gap && hostRect.top - height / 2 >= viewportPadding && hostRect.bottom + height / 2 <= view.innerHeight - viewportPadding,
bottom: spaces.bottom >= height + gap && hostRect.right - width >= viewportPadding,
left: spaces.left >= width + gap && hostRect.top - height / 2 >= viewportPadding && hostRect.bottom + height / 2 <= view.innerHeight - viewportPadding,
top: spaces.top >= height + gap && hostRect.right - width >= viewportPadding
};
const placement = PLACEMENTS.find((side) => fits[side]) || PLACEMENTS.find((side) => spaces[side] >= (side === "right" || side === "left" ? width : height) + gap) || "top";
tooltip.classList.remove(...PLACEMENTS.map((side) => placementClassPrefix + "--" + side));
tooltip.classList.add(placementClassPrefix + "--" + placement);
return placement;
}
function bindTooltipPositioning(host, options) {
if (!host) return () => {
};
const reposition = () => positionTooltip(host, options);
host.addEventListener("mouseenter", reposition);
host.addEventListener("focusin", reposition);
return () => {
host.removeEventListener("mouseenter", reposition);
host.removeEventListener("focusin", reposition);
};
}
var SHARED_TOOLTIP_STYLE_ID = "dft-shared-tooltip-styles";
var SHARED_TOOLTIP_HOST_CLASS = "dft-shared-tooltip-host";
var SHARED_TOOLTIP_CLASS = "dft-shared-tooltip";
var SHARED_TOOLTIP_CSS = [
".dft-shared-tooltip-host { position: relative; }",
".dft-shared-tooltip { background: #172b4d; border-radius: 6px; box-shadow: 0 5px 16px rgba(9,30,66,.28); color: #fff; font: 12px/1.5 system-ui,sans-serif; max-width: min(360px,calc(100vw - 16px)); opacity: 0; padding: 8px 10px; pointer-events: none; position: fixed; text-align: left; transition: opacity .15s ease,visibility .15s ease; visibility: hidden; white-space: pre-line; width: max-content; z-index: 2147483647; }",
".dft-shared-tooltip.dft-shared-tooltip--visible { opacity: 1; visibility: visible; }",
".dft-shared-tooltip-host > .dft-shared-tooltip { background: #172b4d; border-radius: 6px; box-shadow: 0 5px 16px rgba(9,30,66,.28); color: #fff; font: 12px/1.5 system-ui,sans-serif; left: auto; max-width: min(360px,calc(100vw - 16px)); opacity: 0; padding: 8px 10px; pointer-events: none; position: absolute; text-align: left; top: calc(100% + 10px); transform: translateY(-4px); transition: opacity .15s ease,transform .15s ease,visibility .15s ease; visibility: hidden; white-space: pre-line; width: max-content; z-index: 100100; }",
".dft-shared-tooltip-host:hover > .dft-shared-tooltip, .dft-shared-tooltip-host:focus-within > .dft-shared-tooltip { opacity: 1; transform: translateY(0); visibility: visible; }",
".dft-shared-tooltip-host > .dft-shared-tooltip--right { left: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(-4px); }",
".dft-shared-tooltip-host > .dft-shared-tooltip--bottom { left: auto; right: 0; top: calc(100% + 10px); transform: translateY(-4px); }",
".dft-shared-tooltip-host > .dft-shared-tooltip--left { left: auto; right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(4px); }",
".dft-shared-tooltip-host > .dft-shared-tooltip--top { bottom: calc(100% + 10px); left: auto; right: 0; top: auto; transform: translateY(4px); }",
".dft-shared-tooltip-host:hover > .dft-shared-tooltip--right, .dft-shared-tooltip-host:focus-within > .dft-shared-tooltip--right, .dft-shared-tooltip-host:hover > .dft-shared-tooltip--left, .dft-shared-tooltip-host:focus-within > .dft-shared-tooltip--left { transform: translateY(-50%) translateX(0); }",
".dft-shared-tooltip-host:hover > .dft-shared-tooltip--bottom, .dft-shared-tooltip-host:focus-within > .dft-shared-tooltip--bottom, .dft-shared-tooltip-host:hover > .dft-shared-tooltip--top, .dft-shared-tooltip-host:focus-within > .dft-shared-tooltip--top { transform: translateY(0); }"
].join("\n");
function installSharedTooltipStyles(document2) {
if (!document2?.head || document2.getElementById(SHARED_TOOLTIP_STYLE_ID)) return;
const style = document2.createElement("style");
style.id = SHARED_TOOLTIP_STYLE_ID;
style.textContent = SHARED_TOOLTIP_CSS;
document2.head.appendChild(style);
}
function isCustomTooltipElement(element) {
return Boolean(element.closest?.([".ado-plus-action-tooltip-host", ".confluence-plus-label-title-tooltip", ".confluence-plus-label-apply-tooltip-host"].join(",")));
}
function positionSharedPortalTooltip(host, tooltip) {
const view = host.ownerDocument?.defaultView || globalThis.window;
if (!view) return;
const rect = host.getBoundingClientRect();
tooltip.style.left = "-10000px";
tooltip.style.top = "-10000px";
tooltip.classList.add(SHARED_TOOLTIP_CLASS + "--visible");
const size = tooltip.getBoundingClientRect();
const width = size.width || 280;
const height = size.height || 48;
const gap = 10;
const padding = 8;
const spaces = { right: view.innerWidth - rect.right, bottom: view.innerHeight - rect.bottom, left: rect.left, top: rect.top };
const fits = {
right: spaces.right >= width + gap && rect.top - height / 2 >= padding && rect.bottom + height / 2 <= view.innerHeight - padding,
bottom: spaces.bottom >= height + gap && rect.right - width >= padding,
left: spaces.left >= width + gap && rect.top - height / 2 >= padding && rect.bottom + height / 2 <= view.innerHeight - padding,
top: spaces.top >= height + gap && rect.right - width >= padding
};
const placement = PLACEMENTS.find((side) => fits[side]) || "top";
const positions = { right: [rect.right + gap, rect.top + (rect.height - height) / 2], bottom: [rect.right - width, rect.bottom + gap], left: [rect.left - width - gap, rect.top + (rect.height - height) / 2], top: [rect.right - width, rect.top - height - gap] };
const [left, top] = positions[placement];
tooltip.classList.remove(...PLACEMENTS.map((side) => SHARED_TOOLTIP_CLASS + "--" + side));
tooltip.classList.add(SHARED_TOOLTIP_CLASS + "--" + placement, SHARED_TOOLTIP_CLASS + "--visible");
tooltip.style.left = Math.max(padding, Math.min(left, view.innerWidth - width - padding)) + "px";
tooltip.style.top = Math.max(padding, Math.min(top, view.innerHeight - height - padding)) + "px";
}
function setSharedTooltip(element, content) {
if (!element || !content || element.tagName === "OPTION") return;
installSharedTooltipStyles(element.ownerDocument);
element.removeAttribute("title");
element.classList.add(SHARED_TOOLTIP_HOST_CLASS);
let tooltip = element.__dftSharedTooltip;
if (!tooltip) {
tooltip = element.ownerDocument.createElement("span");
tooltip.className = SHARED_TOOLTIP_CLASS;
tooltip.setAttribute("role", "tooltip");
element.__dftSharedTooltip = tooltip;
(element.ownerDocument.body || element.ownerDocument.documentElement).appendChild(tooltip);
}
tooltip.textContent = String(content);
if (!element.__dftSharedTooltipBound) {
element.__dftSharedTooltipBound = true;
const show = () => positionSharedPortalTooltip(element, tooltip);
const hide = () => {
tooltip.classList.remove(SHARED_TOOLTIP_CLASS + "--visible");
};
element.addEventListener("mouseenter", show);
element.addEventListener("focusin", show);
element.addEventListener("mouseleave", hide);
element.addEventListener("focusout", hide);
}
}
function convertNativeTitles(document2, root = document2) {
if (!root?.querySelectorAll) return;
const elements = [];
if (root.nodeType === 1 && root.hasAttribute?.("title")) elements.push(root);
elements.push(...root.querySelectorAll("[title]"));
elements.forEach((element) => {
const content = element.getAttribute("title");
if (!content) return;
if (isCustomTooltipElement(element)) {
element.removeAttribute("title");
return;
}
if (element.tagName === "OPTION") {
const select = element.parentElement;
if (select?.tagName === "SELECT" && select.selectedOptions[0] === element) setSharedTooltip(select.parentElement || select, content);
element.removeAttribute("title");
return;
}
setSharedTooltip(element, content);
});
}
function installSharedTooltips(document2 = globalThis.document) {
if (!document2 || document2.__dftSharedTooltipsInstalled) return;
document2.__dftSharedTooltipsInstalled = true;
installSharedTooltipStyles(document2);
convertNativeTitles(document2);
const observer = new MutationObserver((mutations) => mutations.forEach((mutation) => {
if (mutation.type === "childList") mutation.addedNodes.forEach((node) => convertNativeTitles(document2, node));
if (mutation.type === "attributes" && mutation.attributeName === "title") convertNativeTitles(document2, mutation.target);
}));
observer.observe(document2.documentElement, { attributes: true, attributeFilter: ["title"], childList: true, subtree: true });
}
// src/plugins/confluence/runtime/ui.js
function createUI({ Config: Config2, Store: Store2, Utils: Utils2, Logger: Logger2, Settings: Settings2, AIConfig: AIConfig2, AzureOpenAI: AzureOpenAI2, ConfluenceApi: ConfluenceApi2, AILabelService: AILabelService2, SpaceTreeService: SpaceTreeService2, ContentSessionCache: ContentSessionCache2, getLocaleText: getLocaleText2, images: images2, windowIcons: windowIcons2 }) {
const UI2 = (() => {
const labelPopconfirm = createPopconfirmController({ getThemeClass: () => root?.classList.contains("confluence-plus-theme-dark"), className: "confluence-plus-popconfirm", eventNamespace: "confluencePlusPopconfirm" });
const confluenceToast = createToastController({ state: { toastTimer: -1 }, selector: "[data-confluence-toast-stack]", className: "confluence-plus-toast", getLocaleText: getLocaleText2, getThemeClass: () => root?.classList.contains("confluence-plus-theme-dark"), closeLabelKey: "close" });
let root;
let unsubscribe;
let overlay;
let contextMenu;
let adoStyleFab;
let adoStyleConfig;
let themeMediaQuery;
let themeChangeHandler;
let unsubscribeThemeChange;
let contentFeatureUi = {
spaceTreeOpen: false,
aiLabelsOpen: false
};
let lastViewport = {
width: window.innerWidth,
height: window.innerHeight
};
function applyLayout() {
if (!root) return;
const ui = Store2.getUi();
root.classList.toggle("confluence-plus-expanded", !ui.minimized);
root.style.position = "fixed";
root.style.zIndex = "999999";
if (ui.minimized) {
root.style.left = "0px";
root.style.top = "0px";
root.style.right = "auto";
root.style.bottom = "auto";
return;
}
if (ui.maximized) {
root.style.left = "0px";
root.style.top = "0px";
root.style.right = "auto";
root.style.bottom = "auto";
root.style.width = `${window.innerWidth}px`;
root.style.height = `${window.innerHeight}px`;
return;
}
root.style.width = `${ui.width}px`;
root.style.height = `${ui.height}px`;
root.style.right = "auto";
root.style.bottom = "auto";
root.style.left = `${ui.x}px`;
root.style.top = `${ui.y}px`;
}
function centerPanel() {
const ui = Store2.getUi();
const width = Math.min(
ui.width,
Math.max(Config2.ui.minWidth, window.innerWidth - 24)
);
const height = Math.min(
ui.height,
Math.max(Config2.ui.minHeight, window.innerHeight - 24)
);
const x = Math.max(0, Math.round((window.innerWidth - width) / 2));
const y = Math.max(0, Math.round((window.innerHeight - height) / 2));
Store2.updateUi({
x,
y,
width,
height
});
}
function setMinimized(minimized) {
Store2.updateUi({ minimized });
render();
}
function updateWindowChrome() {
if (!root) return;
applyLayout();
const button = root.querySelector("#confluence-plus-toggle-maximize");
if (!button) return;
const maximized = Store2.getUi().maximized;
button.innerHTML = maximized ? windowIcons2.restore : windowIcons2.maximize;
button.title = getLocaleText2(maximized ? "restoreWindow" : "maximizeWindow");
button.setAttribute(
"aria-label",
getLocaleText2(maximized ? "restoreWindow" : "maximizeWindow")
);
}
function toggleMaximized() {
const ui = Store2.getUi();
if (ui.maximized) {
Store2.updateUi({
maximized: false,
width: ui._normalWidth || ui.width,
height: ui._normalHeight || ui.height,
x: ui._normalX ?? ui.x,
y: ui._normalY ?? ui.y
});
} else {
Store2.updateUi({
maximized: true,
_normalWidth: ui.width,
_normalHeight: ui.height,
_normalX: ui.x,
_normalY: ui.y
});
}
updateWindowChrome();
}
function showMenuOverlay() {
if (!overlay) {
overlay = document.createElement("div");
overlay.id = "confluence-plus-menu-overlay";
overlay.addEventListener("click", () => {
setMinimized(true);
});
document.body.appendChild(overlay);
}
overlay.style.display = "block";
}
function hideMenuOverlay() {
if (overlay) {
overlay.style.display = "none";
}
}
function getCurrentSpaceIconUrl() {
const icon = document.querySelector(
'img[data-testid="space-icon"], img[data-vc="space-header-icon-image"], [data-testid="space-icon"]'
);
const source = icon?.currentSrc || icon?.src || icon?.getAttribute?.("src") || "";
if (!source) return images2.plugin;
try {
return new URL(source, location.href).href;
} catch {
return source;
}
}
function applyContentWindowSpaceIcon() {
const icon = root?.querySelector("[data-confluence-plus-space-icon]");
if (!icon) return;
const fallback = images2.plugin;
icon.src = getCurrentSpaceIconUrl() || fallback;
icon.alt = getLocaleText2("currentSpaceIcon");
icon.onerror = () => {
if (icon.src !== fallback) icon.src = fallback;
};
}
function renderFloatingButtonLocale() {
const tooltip = adoStyleFab?.querySelector(".confluence-plus-tooltip");
if (!tooltip) return;
tooltip.innerHTML = `${getLocaleText2("leftClick")}
${getLocaleText2("rightClick")}`;
}
function applyFloatingButtonPosition() {
if (!adoStyleFab) return;
adoStyleFab.style.right = `${Settings2.get().general.floatingButtonRight}px`;
renderFloatingButtonLocale();
}
function applyConfigTheme() {
const settings = Settings2.get();
const isDark = resolveTheme(
settings.general.theme,
Boolean(window.matchMedia?.("(prefers-color-scheme: dark)").matches)
) === "dark";
const themeClass = isDark ? "confluence-plus-theme-dark" : "confluence-plus-theme-light";
[root, adoStyleFab, adoStyleConfig].forEach((element) => {
element?.classList.remove(
"confluence-plus-theme",
"confluence-plus-theme-light",
"confluence-plus-theme-dark"
);
element?.classList.add("confluence-plus-theme", themeClass);
});
}
function bindThemeListener() {
if (!window.matchMedia || themeMediaQuery) return;
themeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
themeChangeHandler = () => {
if (Settings2.get().general.theme === "system") {
applyConfigTheme();
}
};
unsubscribeThemeChange = subscribeThemeChange(themeMediaQuery, themeChangeHandler);
}
function unbindThemeListener() {
if (!themeMediaQuery || !themeChangeHandler) return;
unsubscribeThemeChange?.();
unsubscribeThemeChange = null;
themeMediaQuery = null;
themeChangeHandler = null;
}
function closeAdoStyleConfig() {
if (!adoStyleConfig) return;
adoStyleConfig.hidden = true;
adoStyleConfig.setAttribute("aria-hidden", "true");
}
function getContentAiSummaryStatus() {
const config = AIConfig2.get();
const configured = Boolean(
config.endpoint.trim() && config.apiKey.trim() && config.deployment.trim()
);
return {
key: configured ? "ai-status-available" : "ai-status-unconfigured",
label: configured ? getLocaleText2("aiStatusAvailable") : getLocaleText2("aiStatusNotConfigured")
};
}
function renderContentAiDeployments() {
const options = adoStyleConfig?.querySelector("#confluencePlusContentAiDeploymentOptions");
if (!options) return;
options.innerHTML = "";
AIConfig2.get().deployments.forEach((deployment) => {
if (!deployment?.id) return;
const option = document.createElement("option");
option.value = deployment.id;
option.label = deployment.label || deployment.id;
options.appendChild(option);
});
}
function getContentAiConnectionMessage(check) {
if (!check) return getLocaleText2("notTested");
if (check.status === "success" && Number.isFinite(check.deploymentCount)) {
return getLocaleText2("deploymentVerified");
}
return check.message || getLocaleText2("notTested");
}
function refreshContentAiSummary() {
const summary = adoStyleConfig?.querySelector("#confluencePlusContentAiStatus");
if (!summary) return;
const status = getContentAiSummaryStatus();
summary.className = `confluence-plus-ai-status ${status.key}`;
summary.textContent = status.label;
}
function bindContentAiControls() {
if (!adoStyleConfig) return;
const endpoint = adoStyleConfig.querySelector("#confluencePlusContentAiEndpoint");
const apiKey = adoStyleConfig.querySelector("#confluencePlusContentAiKey");
const apiVersion = adoStyleConfig.querySelector("#confluencePlusContentAiApiVersion");
const deployment = adoStyleConfig.querySelector("#confluencePlusContentAiDeployment");
const tokenParameter = adoStyleConfig.querySelector("#confluencePlusContentAiTokenParameter");
const result = adoStyleConfig.querySelector("#confluencePlusContentAiResult");
const testButton = adoStyleConfig.querySelector("[data-content-ai-test]");
const clearButton = adoStyleConfig.querySelector("[data-content-ai-clear]");
const showKeyButton = adoStyleConfig.querySelector("[data-content-ai-show-key]");
const setResult = (message, status = "") => {
result.className = `confluence-plus-ai-result${status ? ` ai-result-${status}` : ""}`;
result.textContent = message;
};
const current = AIConfig2.get();
endpoint.value = current.endpoint;
apiKey.value = current.apiKey;
apiVersion.value = current.apiVersion || "2025-03-01-preview";
deployment.value = current.deployment;
tokenParameter.value = current.tokenParameter || "max_completion_tokens";
renderContentAiDeployments();
if (current.lastCheck) {
setResult(
getContentAiConnectionMessage({
...current.lastCheck,
status: current.lastCheck.ok ? "success" : "error"
}),
current.lastCheck.ok ? "success" : "error"
);
}
const updateDraft = () => {
AIConfig2.updateDraft({
endpoint: endpoint.value,
apiKey: apiKey.value,
apiVersion: apiVersion.value,
deployment: deployment.value,
tokenParameter: tokenParameter.value
});
refreshContentAiSummary();
};
[endpoint, apiKey, apiVersion, deployment, tokenParameter].forEach((field) => {
field.addEventListener("input", updateDraft);
field.addEventListener("change", updateDraft);
});
showKeyButton.addEventListener("click", () => {
const visible = apiKey.type === "text";
apiKey.type = visible ? "password" : "text";
showKeyButton.textContent = getLocaleText2(visible ? "show" : "hide");
});
clearButton.addEventListener("click", () => {
AIConfig2.clear();
endpoint.value = "";
apiKey.value = "";
apiVersion.value = "2025-03-01-preview";
deployment.value = "";
tokenParameter.value = "max_completion_tokens";
renderContentAiDeployments();
setResult(getLocaleText2("notTested"));
refreshContentAiSummary();
});
testButton.addEventListener("click", async () => {
updateDraft();
const draft = AIConfig2.get();
if (!draft.endpoint || !draft.apiKey) {
setResult(getLocaleText2("missingEndpointOrKey"), "error");
return;
}
testButton.disabled = true;
setResult(getLocaleText2("connecting"));
try {
const candidate = AIConfig2.validate(draft);
const connection = await AzureOpenAI2.testConnection(candidate);
const selectedDeployment = deployment.value.trim() || connection.deployments[0]?.id || "";
AIConfig2.updateDraft({
...candidate,
deployment: selectedDeployment,
deployments: connection.deployments
});
AIConfig2.recordHealth(true, connection.message, {
deploymentCount: connection.deploymentCount,
v1: AzureOpenAI2.isV1Endpoint(candidate.endpoint)
});
deployment.value = selectedDeployment;
renderContentAiDeployments();
setResult(connection.message, "success");
refreshContentAiSummary();
} catch (error) {
AIConfig2.recordHealth(false, error.message);
setResult(error.message, "error");
refreshContentAiSummary();
} finally {
testButton.disabled = false;
}
});
}
function renderLocale() {
if (!adoStyleConfig) return;
adoStyleConfig.querySelectorAll("[data-confluence-plus-locale]").forEach((element) => {
const key = element.dataset.confluencePlusLocale;
const attribute = element.dataset.confluencePlusLocaleAttr;
if (attribute) {
element.setAttribute(attribute, getLocaleText2(key));
} else {
element.textContent = getLocaleText2(key);
}
});
const title = adoStyleConfig.querySelector("#confluencePlusConfigTitle");
if (title) {
title.textContent = `${getLocaleText2("settings")} (v${GM_info.script.version})`;
}
adoStyleConfig.querySelectorAll("[data-confluence-plus-option-locale]").forEach((option) => {
option.textContent = getLocaleText2(option.dataset.confluencePlusOptionLocale);
});
const language = adoStyleConfig.querySelector("#confluencePlusConfigLanguage");
const theme = adoStyleConfig.querySelector("#confluencePlusConfigTheme");
if (language) language.setAttribute("aria-label", getLocaleText2("language"));
if (theme) theme.setAttribute("aria-label", getLocaleText2("theme"));
const showKey = adoStyleConfig.querySelector("[data-content-ai-show-key]");
if (showKey) {
const keyInput = adoStyleConfig.querySelector("#confluencePlusContentAiKey");
showKey.textContent = getLocaleText2(keyInput?.type === "text" ? "hide" : "show");
}
const current = AIConfig2.get();
const result = adoStyleConfig.querySelector("#confluencePlusContentAiResult");
if (result && current.lastCheck) {
result.textContent = getContentAiConnectionMessage({
...current.lastCheck,
status: current.lastCheck.ok ? "success" : "error"
});
result.removeAttribute("data-confluence-plus-locale");
}
}
function renderAdoStyleConfig() {
if (!adoStyleConfig) return;
const language = Settings2.get().general.language;
const theme = Settings2.get().general.theme;
const right = Settings2.get().general.floatingButtonRight;
const aiConfig = AIConfig2.get();
const aiStatus = getContentAiSummaryStatus();
const option = (value, labelKey) => ``;
const themeOption = (value, labelKey) => ``;
adoStyleConfig.innerHTML = `
${getLocaleText2("general")}
${getLocaleText2("language")}
${getLocaleText2("languageDescription")}
${getLocaleText2("theme")}
${getLocaleText2("themeDescription")}
${getLocaleText2("content")}
${getLocaleText2("aiConfiguration")}
${getLocaleText2("aiConfigurationDescription")}
${aiStatus.label}
`;
adoStyleConfig.querySelectorAll("[data-confluence-plus-ado-close]").forEach((element) => {
element.addEventListener("click", closeAdoStyleConfig);
});
adoStyleConfig.querySelector("#confluencePlusConfigLanguage")?.addEventListener(
"change",
(event) => {
Settings2.updateGeneral("language", event.currentTarget.value);
renderLocale();
applyConfigTheme();
renderFloatingButtonLocale();
if (!Store2.getUi().minimized) {
render(true);
}
console.log(
"[Confluence Plus] Configuration dialog language-updated HTML:\n" + adoStyleConfig.outerHTML
);
}
);
adoStyleConfig.querySelector("#confluencePlusConfigTheme")?.addEventListener(
"change",
(event) => {
Settings2.updateGeneral("theme", event.currentTarget.value);
renderLocale();
applyConfigTheme();
console.log(
"[Confluence Plus] Configuration dialog theme-updated HTML:\n" + adoStyleConfig.outerHTML
);
}
);
adoStyleConfig.querySelector("#confluencePlusConfigFloatingRight")?.addEventListener(
"change",
(event) => {
Settings2.updateGeneral("floatingButtonRight", event.currentTarget.value);
applyFloatingButtonPosition();
event.currentTarget.value = Settings2.get().general.floatingButtonRight;
}
);
bindContentAiControls();
applyConfigTheme();
renderFloatingButtonLocale();
console.log(
"[Confluence Plus] Configuration dialog rendered HTML:\n" + adoStyleConfig.outerHTML
);
}
function openAdoStyleConfig() {
if (!adoStyleConfig) return;
renderAdoStyleConfig();
adoStyleConfig.hidden = false;
adoStyleConfig.setAttribute("aria-hidden", "false");
applyConfigTheme();
adoStyleConfig.querySelector(".confluence-plus-ado-modal-close")?.focus();
}
function createAdoStyleEntry() {
if (adoStyleFab) return;
adoStyleFab = document.createElement("div");
adoStyleFab.id = "confluencePlusStart";
adoStyleFab.className = "confluence-plus-floating-icon confluence-plus-tooltip-host";
adoStyleFab.setAttribute("aria-label", "Confluence Plus");
adoStyleFab.innerHTML = `
Left-click: Open feature window
Right-click: Open configuration window
`;
adoStyleFab.addEventListener("click", (event) => {
if (event.button === 0) openMenu();
});
adoStyleFab.addEventListener("contextmenu", (event) => {
event.preventDefault();
event.stopPropagation();
openAdoStyleConfig();
});
document.body.appendChild(adoStyleFab);
bindTooltipPositioning(adoStyleFab, {
tooltipSelector: ".confluence-plus-tooltip",
placementClassPrefix: "confluence-plus-tooltip"
});
adoStyleConfig = document.createElement("div");
adoStyleConfig.id = "confluencePlusConfigDialog";
adoStyleConfig.className = "confluence-plus-ado-modal";
adoStyleConfig.hidden = true;
adoStyleConfig.setAttribute("aria-hidden", "true");
document.body.appendChild(adoStyleConfig);
bindThemeListener();
applyFloatingButtonPosition();
applyConfigTheme();
}
function removeAdoStyleEntry() {
closeAdoStyleConfig();
adoStyleConfig?.remove();
adoStyleFab?.remove();
adoStyleConfig = null;
adoStyleFab = null;
}
function renderSpaceTreePage(container) {
const target = container;
const isBlogPostRoute = Utils2.isBlogPostUrl(location.href);
let pageId = isBlogPostRoute ? "" : Store2.getContext().pageId;
const treeCurrentPageId = pageId;
target.innerHTML = `
${getLocaleText2("loadingSpaceTree")}
`;
const status = target.querySelector("#confluence-plus-tree-status");
const treeContainer = target.querySelector("#confluence-plus-space-tree");
let treeState = null;
let currentSpaceId = "";
let currentSpaceKey = "";
let currentSpaceName = "";
let loadedPages = [];
function persistTreeCache() {
if (!currentSpaceId || !treeState) return;
ContentSessionCache2.saveSpaceTree(currentSpaceId, {
version: 1,
spaceId: currentSpaceId,
spaceKey: currentSpaceKey,
spaceName: currentSpaceName,
pages: loadedPages,
expandedIds: [...treeState.expanded],
loadedAt: Date.now()
});
}
function renderNodes(nodes) {
const list = document.createElement("ul");
list.className = "confluence-plus-tree-list";
nodes.forEach((node) => {
const item = document.createElement("li");
item.className = "confluence-plus-tree-item";
item.dataset.pageId = node.id;
item.dataset.spaceKey = currentSpaceKey;
item.dataset.nodeType = node.page?.type || "page";
const row = document.createElement("div");
row.className = "confluence-plus-tree-row";
if (node.id === String(treeCurrentPageId)) {
row.classList.add("confluence-plus-tree-current");
row.dataset.treeCurrent = "1";
}
if (node.children.length) {
const toggle = document.createElement("button");
const isExpanded = treeState.expanded.has(node.id);
toggle.className = "confluence-plus-tree-toggle";
toggle.innerText = isExpanded ? "\u25BE" : "\u25B8";
toggle.title = isExpanded ? getLocaleText2("collapseChildren") : getLocaleText2("expandChildren");
toggle.addEventListener("click", () => {
if (treeState.expanded.has(node.id)) {
treeState.expanded.delete(node.id);
} else {
treeState.expanded.add(node.id);
}
persistTreeCache();
renderTree(true);
});
row.appendChild(toggle);
} else {
const spacer = document.createElement("span");
spacer.className = "confluence-plus-tree-toggle-spacer";
row.appendChild(spacer);
}
const titleWrap = document.createElement("span");
titleWrap.className = "confluence-plus-tree-title-wrap";
const title = document.createElement("span");
title.className = "confluence-plus-tree-title";
title.innerText = node.title;
title.title = `${node.title} (${node.id})`;
titleWrap.appendChild(title);
if (node.children.length > 0) {
const childCount = document.createElement("span");
childCount.className = "confluence-plus-tree-child-count";
childCount.innerText = `(${node.children.length})`;
childCount.title = getLocaleText2("childPages", {
count: node.children.length
});
titleWrap.appendChild(childCount);
}
if (node.id === String(treeCurrentPageId)) {
const badge = document.createElement("span");
badge.className = "confluence-plus-tree-current-badge";
badge.innerText = getLocaleText2("currentPageBadge");
titleWrap.appendChild(badge);
}
row.appendChild(titleWrap);
item.appendChild(row);
if (node.children.length && treeState.expanded.has(node.id)) {
item.appendChild(renderNodes(node.children));
}
list.appendChild(item);
});
return list;
}
function renderTree(preserveScroll = false) {
const scrollTop = preserveScroll ? treeContainer.scrollTop : 0;
treeContainer.innerHTML = "";
if (!treeState.roots.length) {
treeContainer.innerText = getLocaleText2("noPagesInSpace");
return;
}
treeContainer.appendChild(renderNodes(treeState.roots));
if (preserveScroll) {
treeContainer.scrollTop = scrollTop;
return;
}
const current = treeContainer.querySelector(
'[data-tree-current="1"]'
);
current?.scrollIntoView({
block: "center",
behavior: "smooth"
});
}
async function loadTree(forceRefresh = false) {
status.className = "confluence-plus-modal-status";
status.innerText = getLocaleText2("loadingSpaceTree");
treeContainer.innerHTML = "";
try {
let space = null;
if (!Utils2.isValidPageId(pageId)) {
const spaceMatch = location.pathname.match(
/\/spaces\/([^/]+)/i
);
const spaceKeyFromUrl = spaceMatch ? decodeURIComponent(spaceMatch[1]) : "";
if (spaceKeyFromUrl) {
space = await ConfluenceApi2.spaces.getByKey(
spaceKeyFromUrl
);
pageId = String(
space?.homepageId || space?.homepage?.id || ""
);
}
}
if (!Utils2.isValidPageId(pageId)) {
throw new Error(getLocaleText2("invalidPageId"));
}
const currentPage = await ConfluenceApi2.pages.get(pageId);
const spaceId = currentPage.spaceId || space?.id;
if (!spaceId) {
throw new Error(getLocaleText2("missingSpaceId"));
}
if (!space || String(space.id) !== String(spaceId)) {
space = await ConfluenceApi2.spaces.get(spaceId);
}
if (!isBlogPostRoute) {
Store2.setContext({ pageId });
}
const spaceName = (space.name || "").trim();
currentSpaceId = String(spaceId);
currentSpaceName = spaceName;
currentSpaceKey = (space.key || "").trim();
if (!spaceName || !currentSpaceKey) {
throw new Error(getLocaleText2("missingSpaceInfo"));
}
const cached = forceRefresh ? null : ContentSessionCache2.getSpaceTree(currentSpaceId);
if (forceRefresh) {
ContentSessionCache2.clearSpaceTree(currentSpaceId);
}
loadedPages = Array.isArray(cached?.pages) ? cached.pages : await SpaceTreeService2.load(spaceId);
treeState = SpaceTreeService2.buildTree(
loadedPages,
treeCurrentPageId,
spaceName
);
renderTree();
persistTreeCache();
status.className = "confluence-plus-modal-status success";
status.innerText = cached ? getLocaleText2("loadedPagesFromCache", { count: treeState.filteredCount }) : getLocaleText2("loadedPages", { count: treeState.filteredCount });
} catch (error) {
status.className = "confluence-plus-modal-status error";
status.innerText = error.message;
Logger2.error("space-tree.load-failed", error);
}
}
target.querySelector("#confluence-plus-tree-refresh").addEventListener(
"click",
() => loadTree(true)
);
loadTree();
}
function renderAiLabelsPage(container) {
const target = container;
const pageId = Store2.getContext().pageId;
if (!Utils2.isValidPageId(pageId)) {
target.innerHTML = '' + getLocaleText2("invalidPageId") + "
";
return;
}
target.innerHTML = `
`;
const status = target.querySelector("#confluence-plus-label-status");
const results = target.querySelector("#confluence-plus-label-results");
const generateButton = target.querySelector("#confluence-plus-label-generate");
generateButton.disabled = AIConfig2.getStatus().key !== "available";
const cachedLabelState = ContentSessionCache2.getLabels(pageId);
let currentPageTitle = "";
let existingLabels = Array.isArray(cachedLabelState?.existingLabels) ? [...cachedLabelState.existingLabels] : [];
function persistLabelState() {
const rows = [...target.querySelectorAll(".confluence-plus-label-item")];
ContentSessionCache2.saveLabels(pageId, {
version: 1,
pageId: String(pageId),
existingLabels: [...existingLabels],
suggestions: rows.map((row) => ({
value: row.querySelector(".confluence-plus-label-edit")?.value || "",
checked: Boolean(row.querySelector('input[type="checkbox"]')?.checked)
})),
manualDraft: target.querySelector("#confluence-plus-label-manual")?.value || "",
status: {
className: status.className,
message: status.innerText
},
updatedAt: Date.now()
});
}
function setLabelStatus(message, type = "") {
status.className = `confluence-plus-modal-status${type ? ` ${type}` : ""}`;
status.innerText = message;
persistLabelState();
}
results.addEventListener("input", persistLabelState);
results.addEventListener("change", persistLabelState);
function renderSuggestions(suggestions, pageTitle = currentPageTitle) {
results.innerHTML = `
`;
const resultsHeader = results.querySelector(
"[data-confluence-plus-label-title-tooltip]"
);
const headerText = pageTitle ? getLocaleText2("recommendedLabelsFor", { title: pageTitle }) : getLocaleText2("recommendedLabels");
resultsHeader.querySelector(".confluence-plus-label-results-header-text").textContent = headerText;
resultsHeader.dataset.confluencePlusTooltip = pageTitle || headerText;
resultsHeader.title = pageTitle || headerText;
const list = results.querySelector("#confluence-plus-label-list");
const applyButton = results.querySelector("#confluence-plus-label-apply");
function getApplicableLabels() {
const selected = [...list.querySelectorAll(".confluence-plus-label-item")].filter((row) => row.querySelector('input[type="checkbox"]').checked).map((row) => AILabelService2.normalize(
row.querySelector(".confluence-plus-label-edit").value
)).filter(Boolean);
const existing = new Set(
existingLabels.map((label) => AILabelService2.normalize(label))
);
return [...new Set(selected)].filter((label) => !existing.has(label));
}
function updateApplyButton() {
const canApply = getApplicableLabels().length > 0;
applyButton.disabled = !canApply;
applyButton.title = canApply ? getLocaleText2("applySelectedLabelsTitle") : getLocaleText2("noLabelsToApplyTooltip");
applyButton.parentElement.dataset.tooltip = applyButton.title;
applyButton.parentElement.setAttribute("aria-label", applyButton.title);
applyButton.setAttribute("aria-disabled", String(!canApply));
}
function appendLabel(label, checked = true) {
const normalized = AILabelService2.normalize(label);
if (!normalized || list.querySelector(
`[data-label="${CSS.escape(normalized)}"]`
)) {
return;
}
const row = document.createElement("label");
row.className = "confluence-plus-label-item";
row.dataset.label = normalized;
row.innerHTML = `
`;
row.querySelector(".confluence-plus-label-remove").addEventListener(
"click",
() => {
row.remove();
persistLabelState();
updateApplyButton();
}
);
list.appendChild(row);
}
suggestions.forEach((suggestion) => {
const value = typeof suggestion === "string" ? suggestion : suggestion?.value;
const checked = typeof suggestion === "string" ? true : suggestion?.checked !== false;
appendLabel(value, checked);
});
updateApplyButton();
results.querySelector("#confluence-plus-label-add-manual").addEventListener(
"click",
() => {
const input = results.querySelector("#confluence-plus-label-manual");
appendLabel(input.value);
input.value = "";
persistLabelState();
updateApplyButton();
}
);
results.addEventListener("input", updateApplyButton);
results.addEventListener("change", updateApplyButton);
function showLabelConfirmation(labels, anchor) {
return labelPopconfirm.show({
anchor,
title: getLocaleText2(labels.length === 1 ? "applyOneLabelQuestion" : "applyManyLabelsQuestion", void 0, { count: labels.length }),
message: "",
confirmText: getLocaleText2("apply"),
cancelText: getLocaleText2("cancel")
});
}
results.querySelector("#confluence-plus-label-apply").addEventListener(
"click",
async () => {
const labels = getApplicableLabels();
if (!labels.length) {
setLabelStatus(getLocaleText2("selectNewValidLabel"), "error");
updateApplyButton();
return;
}
const confirmed = await showLabelConfirmation(
labels,
applyButton
);
if (!confirmed) {
return;
}
applyButton.disabled = true;
setLabelStatus(getLocaleText2("applyingLabels"));
try {
await ConfluenceApi2.pages.addLabels(pageId, labels);
existingLabels.push(...labels);
applyButton.disabled = true;
setLabelStatus(
getLocaleText2("addedLabelsRefreshing", { count: labels.length }),
"success"
);
ContentSessionCache2.clearLabels(pageId);
Logger2.info("ai-labels.applied", { pageId, labels });
setTimeout(() => location.reload(), 800);
} catch (error) {
setLabelStatus(error.message, "error");
Logger2.error("ai-labels.apply-failed", error);
applyButton.disabled = false;
}
}
);
}
function localizeCachedLabelStatus(message) {
const generated = String(message || "").match(/^Generated (\d+) suggestion\(s\)$/);
return generated ? getLocaleText2("generatedSuggestions", { count: Number(generated[1]) }) : message;
}
if (cachedLabelState) {
if (Array.isArray(cachedLabelState.suggestions)) {
renderSuggestions(cachedLabelState.suggestions);
}
const manualDraft = target.querySelector("#confluence-plus-label-manual");
if (manualDraft) {
manualDraft.value = cachedLabelState.manualDraft || "";
}
if (cachedLabelState.status?.message) {
status.className = cachedLabelState.status.className || "confluence-plus-modal-status";
status.innerText = localizeCachedLabelStatus(cachedLabelState.status.message);
}
}
generateButton.addEventListener("click", async () => {
if (!AIConfig2.isConfigured()) {
setLabelStatus(getLocaleText2("configureAiBeforeLabels"), "error");
return;
}
generateButton.disabled = true;
setLabelStatus(getLocaleText2("loadingAndGenerating"));
results.innerHTML = "";
persistLabelState();
try {
const result = await AILabelService2.generate(pageId);
existingLabels = Array.isArray(result.existingLabels) ? [...result.existingLabels] : [];
currentPageTitle = result.page.title || getLocaleText2("currentPageBadge");
renderSuggestions(result.suggestions, currentPageTitle);
setLabelStatus(
result.suggestions.length ? getLocaleText2("generatedSuggestions", {
count: result.suggestions.length
}) : getLocaleText2("noNewLabelSuggestions"),
"success"
);
persistLabelState();
Logger2.info("ai-labels.generated", {
pageId,
count: result.suggestions.length
});
} catch (error) {
setLabelStatus(error.message, "error");
Logger2.error("ai-labels.generate-failed", error);
} finally {
generateButton.disabled = false;
}
});
}
function openMenu() {
const ui = Store2.getUi();
Store2.updateUi({
width: Math.max(ui.width, 720),
height: Math.max(ui.height, 520)
});
centerPanel();
Store2.updateUi({ minimized: false });
render();
}
function renderMenu() {
const maximized = Store2.getUi().maximized;
root.innerHTML = `
${getLocaleText2("spacePageTree")}
${getLocaleText2("spacePageTreeDescription")}
${getLocaleText2("generateLabel")}
${getLocaleText2("aiConfigurationRequired")}
${getLocaleText2("generateLabelDescription")}
`;
applyContentWindowSpaceIcon();
root.querySelector("#confluence-plus-close-btn").innerHTML = windowIcons2.close;
root.querySelector("#confluence-plus-close-btn").addEventListener(
"click",
() => setMinimized(true)
);
const spaceTreeFeature = root.querySelector("#confluence-plus-space-tree-feature");
const aiLabelFeature = root.querySelector("#confluence-plus-ai-label-feature");
const spaceTreeHost = root.querySelector("[data-confluence-plus-space-tree-host]");
const aiLabelHost = root.querySelector("[data-confluence-plus-ai-label-host]");
const mountSpaceTree = () => {
if (!spaceTreeFeature.open || spaceTreeFeature.dataset.loaded) return;
spaceTreeFeature.dataset.loaded = "true";
renderSpaceTreePage(spaceTreeHost);
};
const mountAiLabels = () => {
if (!aiLabelFeature.open || aiLabelFeature.dataset.loaded) return;
aiLabelFeature.dataset.loaded = "true";
renderAiLabelsPage(aiLabelHost);
};
spaceTreeFeature.addEventListener("toggle", () => {
contentFeatureUi.spaceTreeOpen = spaceTreeFeature.open;
mountSpaceTree();
});
aiLabelFeature.addEventListener("toggle", () => {
contentFeatureUi.aiLabelsOpen = aiLabelFeature.open;
mountAiLabels();
});
const featureUi = contentFeatureUi;
if (featureUi.spaceTreeOpen) {
spaceTreeFeature.open = true;
mountSpaceTree();
}
if (featureUi.aiLabelsOpen) {
aiLabelFeature.open = true;
mountAiLabels();
}
}
function protectHeaderControls() {
root.querySelectorAll(
".confluence-plus-header-actions, .confluence-plus-header button"
).forEach((element) => {
["mousedown", "pointerdown", "touchstart"].forEach((eventName) => {
element.addEventListener(eventName, (event) => {
event.stopPropagation();
});
});
});
}
function render(preserveLayout = false) {
if (!root) return;
const layoutBeforeRender = preserveLayout ? { ...Store2.getUi() } : null;
const isContentWindow = !Store2.getUi().minimized;
root.classList.toggle("confluence-plus-content-window", isContentWindow);
if (isContentWindow) {
root.setAttribute("role", "dialog");
root.setAttribute("aria-modal", "true");
root.setAttribute("aria-labelledby", "confluence-plus-content-window-title");
} else {
root.removeAttribute("role");
root.removeAttribute("aria-modal");
root.removeAttribute("aria-labelledby");
}
applyConfigTheme();
applyLayout();
if (Store2.getUi().minimized) {
hideMenuOverlay();
root.hidden = true;
root.setAttribute("aria-hidden", "true");
return;
}
root.hidden = false;
root.removeAttribute("aria-hidden");
showMenuOverlay();
renderMenu();
protectHeaderControls();
root.querySelector("#confluence-plus-toggle-maximize")?.addEventListener(
"click",
toggleMaximized
);
if (layoutBeforeRender) {
Store2.updateUi({
x: layoutBeforeRender.x,
y: layoutBeforeRender.y,
width: layoutBeforeRender.width,
height: layoutBeforeRender.height,
maximized: layoutBeforeRender.maximized
});
applyLayout();
}
}
function updatePageId() {
const input = root?.querySelector("#confluence-plus-current-page-id");
if (input) {
input.value = Store2.getContext().pageId;
}
}
function toast(message, type = "info") {
confluenceToast.show({ level: type === "error" ? "error" : type === "success" ? "success" : "info", message });
}
function hideContextMenu() {
contextMenu?.remove();
contextMenu = null;
}
function showContextMenu(event) {
if (Store2.getUi().minimized) return;
const target = event.target && typeof event.target.closest === "function" ? event.target : null;
const treeRow = target?.closest(".confluence-plus-tree-row");
const treeItem = treeRow?.closest(".confluence-plus-tree-item");
if (!treeRow || !treeItem || !root?.contains(treeRow)) return;
event.preventDefault();
event.stopPropagation();
hideContextMenu();
const script = typeof GM_info !== "undefined" ? GM_info.script || {} : {};
const name = script.name || "Confluence Plus";
const version = script.version || "unknown";
const pageId = treeItem?.dataset.pageId || "";
const spaceKey = treeItem?.dataset.spaceKey || "";
const nodeType = treeItem?.dataset.nodeType || "page";
const canOpenPage = Boolean(
pageId && spaceKey && nodeType !== "folder"
);
const menuWidth = 220;
const menuHeight = canOpenPage ? 92 : 54;
const left = Math.min(
Math.max(8, event.clientX),
window.innerWidth - menuWidth - 8
);
const top = Math.min(
Math.max(8, event.clientY),
window.innerHeight - menuHeight - 8
);
contextMenu = document.createElement("div");
contextMenu.className = "confluence-plus-context-menu";
contextMenu.setAttribute("role", "menu");
contextMenu.style.left = `${left}px`;
contextMenu.style.top = `${top}px`;
if (canOpenPage) {
const openPage = document.createElement("button");
openPage.type = "button";
openPage.className = "confluence-plus-context-menu-action";
openPage.setAttribute("role", "menuitem");
openPage.textContent = getLocaleText2("openPage");
openPage.addEventListener("click", (clickEvent) => {
clickEvent.preventDefault();
clickEvent.stopPropagation();
hideContextMenu();
const pageUrl = `${location.origin}/wiki/spaces/${encodeURIComponent(spaceKey)}/pages/${pageId}`;
window.open(pageUrl, "_blank", "noopener,noreferrer");
});
contextMenu.appendChild(openPage);
}
const item = document.createElement("div");
item.className = "confluence-plus-context-menu-item confluence-plus-context-menu-info";
item.setAttribute("role", "menuitem");
item.setAttribute("aria-disabled", "true");
item.textContent = `${name} v${version}`;
contextMenu.appendChild(item);
document.body.appendChild(contextMenu);
setTimeout(() => {
document.addEventListener("click", hideContextMenu, {
once: true,
capture: true
});
}, 0);
}
function enableDrag() {
let dragging = false;
let offsetX = 0;
let offsetY = 0;
root.addEventListener("dblclick", (event) => {
if (Store2.getUi().minimized || event.button !== 0) return;
const header = event.target.closest(".confluence-plus-header");
const interactive = event.target.closest(
"button, input, select, textarea, a, .confluence-plus-header-actions"
);
if (!header || interactive) return;
event.preventDefault();
event.stopPropagation();
toggleMaximized();
});
root.addEventListener("mousedown", (event) => {
if (Store2.getUi().minimized || Store2.getUi().maximized || event.button !== 0) return;
const header = event.target.closest(".confluence-plus-header");
const interactive = event.target.closest(
"button, input, select, textarea, a, .confluence-plus-header-actions"
);
if (!header || interactive) {
return;
}
const rect = root.getBoundingClientRect();
dragging = true;
offsetX = event.clientX - rect.left;
offsetY = event.clientY - rect.top;
document.body.style.userSelect = "none";
});
document.addEventListener("mousemove", (event) => {
if (!dragging) return;
const rect = root.getBoundingClientRect();
const maxX = Math.max(0, window.innerWidth - rect.width);
const maxY = Math.max(0, window.innerHeight - rect.height);
const x = Math.min(
maxX,
Math.max(0, event.clientX - offsetX)
);
const y = Math.min(
maxY,
Math.max(0, event.clientY - offsetY)
);
root.style.left = `${x}px`;
root.style.top = `${y}px`;
root.style.right = "auto";
root.style.bottom = "auto";
Store2.updateUi({ x, y });
});
document.addEventListener("mouseup", () => {
if (!dragging) return;
dragging = false;
document.body.style.userSelect = "";
});
}
function observeResize() {
if (!window.ResizeObserver) return;
const observer = new ResizeObserver(
Utils2.debounce(() => {
if (!root || Store2.getUi().minimized) return;
const ui = Store2.getUi();
const rect = root.getBoundingClientRect();
const width = Math.round(rect.width);
const height = Math.round(rect.height);
const fullscreenWidth = Math.round(window.innerWidth);
const fullscreenHeight = Math.round(window.innerHeight);
const isFullscreenGeometry = Math.abs(width - fullscreenWidth) <= 1 && Math.abs(height - fullscreenHeight) <= 1 && Math.abs(rect.left) <= 1 && Math.abs(rect.top) <= 1;
if (!ui.maximized && isFullscreenGeometry) {
Store2.updateUi({
maximized: true,
_normalWidth: ui.width,
_normalHeight: ui.height,
_normalX: ui.x,
_normalY: ui.y
});
updateWindowChrome();
return;
}
if (ui.maximized) {
const userResized = !isFullscreenGeometry;
if (!userResized) return;
Store2.updateUi({
maximized: false,
width,
height,
x: Math.max(0, Math.round(rect.left)),
y: Math.max(0, Math.round(rect.top))
});
updateWindowChrome();
return;
}
Store2.updateUi({ width, height });
})
);
observer.observe(root);
}
function handleViewportResize() {
const viewportChanged = window.innerWidth !== lastViewport.width || window.innerHeight !== lastViewport.height;
lastViewport = {
width: window.innerWidth,
height: window.innerHeight
};
if (!viewportChanged || Store2.getUi().minimized) return;
if (Store2.getUi().maximized) {
Store2.updateUi({ maximized: false });
}
centerPanel();
updateWindowChrome();
}
function init() {
createAdoStyleEntry();
root = document.createElement("div");
root.id = "confluence-plus-root";
root.addEventListener("contextmenu", showContextMenu);
document.body.appendChild(root);
window.addEventListener("resize", handleViewportResize);
render();
enableDrag();
observeResize();
unsubscribe = Store2.subscribe((type) => {
if (type === "context") {
contentFeatureUi = {
spaceTreeOpen: false,
aiLabelsOpen: false
};
updatePageId();
}
if (type === "ui" && Store2.getUi().minimized) {
render();
}
});
}
return {
init,
render,
toast,
destroy() {
unsubscribe?.();
hideContextMenu();
unbindThemeListener();
removeAdoStyleEntry();
window.removeEventListener("resize", handleViewportResize);
root?.remove();
}
};
})();
return UI2;
}
// src/plugins/confluence/runtime/core.js
var images = {
plugin: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NCA2NCI+PGNpcmNsZSBjeD0iMzIiIGN5PSIzMiIgcj0iMjgiIGZpbGw9IiMwYzY2ZTQiLz48cmVjdCB4PSIxNCIgeT0iMjMiIHdpZHRoPSIzNiIgaGVpZ2h0PSIxOCIgcng9IjkiIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzLjUiLz48Y2lyY2xlIGN4PSI0MSIgY3k9IjMyIiByPSI2IiBmaWxsPSIjZmZmIi8+PHBhdGggZD0iTTIwIDMyaDgiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIzIiBzdHJva2UtbGluZWNhcD0icm91bmQiIG9wYWNpdHk9Ii43NSIvPjxwYXRoIGQ9Ik00MSAxOHYtNU00OSAyMWwzLTNNNTIgMzJoNCIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjIuNSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9zdmc+",
configChevron: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTcgMTBsNSA1IDUtNSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBmaWxsPSJub25lIi8+PC9zdmc+"
};
var windowIcons = {
maximize: ``,
restore: ``,
close: ``
};
var Settings = (() => {
const validUiLanguages = ["system", "en", "zh-CN"];
const validThemes = ["system", "light", "dark"];
const defaults = {
version: 1,
general: {
language: Config.settings.defaultLanguage,
theme: Config.settings.defaultTheme,
floatingButtonRight: Config.settings.defaultFloatingButtonRight
},
modules: {
content: {}
}
};
const cloneDefaults = () => JSON.parse(JSON.stringify(defaults));
function normalizeRight(value) {
const number = Number(value);
if (!Number.isFinite(number)) {
return Config.settings.defaultFloatingButtonRight;
}
return Math.min(
Config.settings.maxFloatingButtonRight,
Math.max(Config.settings.minFloatingButtonRight, Math.floor(number))
);
}
function normalize(source = {}) {
const base = cloneDefaults();
const general = source.general || {};
base.general.language = validUiLanguages.includes(general.language) ? general.language : base.general.language;
base.general.theme = validThemes.includes(general.theme) ? general.theme : base.general.theme;
base.general.floatingButtonRight = normalizeRight(
general.floatingButtonRight
);
base.modules.content = {
...source.modules?.content || {}
};
return base;
}
let current = normalize(Storage.loadSettings());
function save() {
Storage.saveSettings(current);
}
return {
validUiLanguages,
validThemes,
get() {
return JSON.parse(JSON.stringify(current));
},
updateGeneral(key, value) {
if (key === "language" && validUiLanguages.includes(value)) {
current.general.language = value;
} else if (key === "theme" && validThemes.includes(value)) {
current.general.theme = value;
} else if (key === "floatingButtonRight") {
current.general.floatingButtonRight = normalizeRight(value);
} else {
return this.get();
}
save();
return this.get();
},
resolveUiLanguage(value = current.general.language) {
return value === "zh-CN" || value === "system" && /^zh(?:-|$)/i.test(navigator.language || "") ? "zh-CN" : "en";
},
reset() {
current = cloneDefaults();
save();
}
};
})();
var { getLocaleText } = createLocaleService({
getLanguage: () => Settings.get().general.language,
localeMessages
});
var AIConfig = createAIConfig({ Config, Storage, getLocaleText });
var Store = createStore({ Config, Storage, Utils });
var Router = createRouter({ Store, Logger, Utils });
var Http = createHttp({ Config, getLocaleText });
var AzureOpenAI = createAzureOpenAI({ Http, AIConfig, getLocaleText });
var ConfluenceApi = createConfluenceApi({ Config, Http });
var AILabelService = createAILabelService({ ConfluenceApi, AzureOpenAI });
var SpaceTreeService = createSpaceTreeService({ ConfluenceApi, Utils, Logger });
var ContentSessionCache = createContentSessionCache({ Logger });
var UI = createUI({ Config, Store, Router, Utils, Logger, Settings, AIConfig, AzureOpenAI, ConfluenceApi, AILabelService, SpaceTreeService, ContentSessionCache, getLocaleText, images, windowIcons });
function bootstrap() {
Store.init();
Router.init();
UI.init();
Logger.info("initialized", {
version: GM_info.script.version,
origin: location.origin
});
}
// src/plugins/confluence/styles.js
function installStyles({ config, addStyle } = {}) {
const applyStyle = addStyle || (typeof GM_addStyle === "function" ? GM_addStyle : null);
if (typeof applyStyle !== "function") {
throw new Error("Confluence Plus requires GM_addStyle to install styles.");
}
applyStyle(` #confluence-plus-menu-overlay {
position: fixed;
inset: 0;
z-index: 99998;
background: rgba(9, 30, 66, .28);
backdrop-filter: blur(1px);
}
.confluence-plus-floating-icon {
position: fixed;
right: 120px;
top: 0;
bottom: auto;
z-index: 100004;
display: inline-flex;
}
.confluence-plus-floating-icon-image {
display: block;
width: 48px;
height: 48px;
cursor: pointer;
object-fit: contain;
}
.confluence-plus-tooltip-host {
position: relative;
}
.confluence-plus-floating-icon.confluence-plus-tooltip-host {
position: fixed;
}
.confluence-plus-tooltip {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 100005;
padding: 8px 10px;
border-radius: 5px;
background: #172b4d;
box-shadow: 0 4px 12px rgba(9, 30, 66, .25);
color: #fff;
font-size: 12px;
line-height: 1.55;
opacity: 0;
pointer-events: none;
transform: translateY(-4px);
transition: opacity .15s ease, transform .15s ease;
visibility: hidden;
white-space: nowrap;
}
.confluence-plus-tooltip-host:hover .confluence-plus-tooltip,
.confluence-plus-tooltip-host:focus-within .confluence-plus-tooltip {
opacity: 1;
transform: translateY(0);
visibility: visible;
}
.confluence-plus-tooltip.confluence-plus-tooltip--right { bottom: auto; left: calc(100% + 10px); right: auto; top: 50%; transform: translateY(-50%) translateX(-4px); }
.confluence-plus-tooltip.confluence-plus-tooltip--bottom { left: auto; right: 0; top: calc(100% + 10px); transform: translateY(-4px); }
.confluence-plus-tooltip.confluence-plus-tooltip--left { left: auto; right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(4px); }
.confluence-plus-tooltip.confluence-plus-tooltip--top { bottom: calc(100% + 10px); right: 0; top: auto; transform: translateY(4px); }
.confluence-plus-tooltip-host:hover .confluence-plus-tooltip--right, .confluence-plus-tooltip-host:focus-within .confluence-plus-tooltip--right, .confluence-plus-tooltip-host:hover .confluence-plus-tooltip--left, .confluence-plus-tooltip-host:focus-within .confluence-plus-tooltip--left { transform: translateY(-50%) translateX(0); }
.confluence-plus-tooltip-host:hover .confluence-plus-tooltip--bottom, .confluence-plus-tooltip-host:focus-within .confluence-plus-tooltip--bottom, .confluence-plus-tooltip-host:hover .confluence-plus-tooltip--top, .confluence-plus-tooltip-host:focus-within .confluence-plus-tooltip--top { transform: translateY(0); }
.confluence-plus-ado-modal {
align-items: center;
display: flex;
inset: 0;
justify-content: center;
position: fixed;
z-index: 100003;
}
.confluence-plus-ado-modal[hidden] {
display: none;
}
.confluence-plus-ado-modal-backdrop {
background: rgba(9, 30, 66, .32);
inset: 0;
position: absolute;
}
.confluence-plus-ado-modal-panel {
align-self: center;
background: var(--confluence-plus-surface, #fff);
border-radius: 9px;
box-shadow: 0 18px 48px rgba(9, 30, 66, .22), 0 3px 12px rgba(9, 30, 66, .12);
color: var(--confluence-plus-text, #172b4d);
display: flex;
flex: 0 1 auto;
flex-direction: column;
height: min(695px, calc(100dvh - 32px));
max-height: calc(100dvh - 32px);
max-width: min(860px, calc(100vw - 36px));
overflow: hidden;
position: relative;
width: 860px;
}
.confluence-plus-ado-modal-header {
align-items: center;
border-bottom: 1px solid var(--confluence-plus-border, #dfe1e6);
display: flex;
flex: 0 0 auto;
gap: 12px;
padding: 18px 20px 16px;
}
.confluence-plus-ado-modal-mark {
border-radius: 8px;
display: block;
flex: none;
height: 36px;
object-fit: cover;
width: 36px;
}
.confluence-plus-ado-modal-header > div {
min-width: 0;
}
.confluence-plus-ado-modal-header strong,
.confluence-plus-ado-modal-header small {
display: block;
}
.confluence-plus-ado-modal-header strong {
font-size: 16px;
line-height: 1.2;
}
.confluence-plus-ado-modal-header small {
color: var(--confluence-plus-muted, #5e6c84);
font-size: 12px;
margin-top: 4px;
}
.confluence-plus-ado-modal-close {
background: transparent;
border: 0;
border-radius: 5px;
color: var(--confluence-plus-muted, #6b778c);
cursor: pointer;
font-size: 22px;
height: 30px;
margin-left: auto;
width: 30px;
}
.confluence-plus-ado-modal-close:hover {
background: var(--confluence-plus-hover, #ebecf0);
color: var(--confluence-plus-text, #172b4d);
}
.confluence-plus-ado-modal-close:focus {
outline: 0;
}
.confluence-plus-ado-modal-body {
flex: 1 1 0;
max-height: none;
min-height: 0;
overflow-y: auto;
padding: 14px;
scrollbar-color: var(--confluence-plus-border, #dfe1e6) transparent;
scrollbar-width: thin;
}
.confluence-plus-ado-modal-body::-webkit-scrollbar {
width: 8px;
}
.confluence-plus-ado-modal-body::-webkit-scrollbar-thumb {
background: var(--confluence-plus-border, #dfe1e6);
border-radius: 999px;
}
.confluence-plus-modal-config-list {
display: grid;
gap: 8px;
min-height: 0;
}
.confluence-plus-modal-config-section {
color: var(--confluence-plus-text, #172b4d);
font-size: 12px;
letter-spacing: .04em;
margin: 4px 2px 0;
text-transform: uppercase;
}
.confluence-plus-modal-config-group {
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 9px;
overflow: hidden;
}
.confluence-plus-modal-config-group[hidden] {
display: none !important;
}
.confluence-plus-modal-config-summary {
align-items: center;
background: var(--confluence-plus-control, #f8fafc);
color: var(--confluence-plus-text, #172b4d);
cursor: pointer;
display: flex;
font-size: 12px;
font-weight: 700;
justify-content: flex-start;
letter-spacing: .04em;
list-style: none;
padding: 9px 36px 9px 12px;
position: relative;
text-transform: uppercase;
}
.confluence-plus-modal-config-summary::-webkit-details-marker {
display: none;
}
.confluence-plus-modal-config-summary:hover {
background: var(--confluence-plus-hover, #f1f5f9);
}
.confluence-plus-modal-config-chevron {
align-items: center;
color: var(--confluence-plus-muted, #5e6c84);
display: inline-flex;
height: 18px;
justify-content: center;
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
transition: transform .18s ease;
width: 18px;
}
.confluence-plus-modal-config-chevron img {
display: block;
height: 16px;
width: 16px;
}
.confluence-plus-modal-config-group[open] .confluence-plus-modal-config-chevron {
transform: translateY(-50%) rotate(180deg);
}
.confluence-plus-modal-config-group-content {
display: grid;
gap: 8px;
padding: 8px;
}
.confluence-plus-modal-config-row {
align-items: center;
background: var(--confluence-plus-control, #f4f5f7);
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 9px;
box-sizing: border-box;
display: grid;
gap: 14px;
grid-template-columns: minmax(0, 1fr) minmax(150px, 42%);
min-height: 60px;
padding: 9px 12px;
}
.confluence-plus-modal-config-copy {
min-width: 0;
}
.confluence-plus-modal-config-title,
.confluence-plus-modal-config-description {
display: block;
}
.confluence-plus-modal-config-title {
color: var(--confluence-plus-text, #172b4d);
font-size: 13px;
font-weight: 650;
}
.confluence-plus-modal-config-description {
color: var(--confluence-plus-muted, #5e6c84);
font-size: 11px;
line-height: 1.35;
margin-top: 4px;
}
.confluence-plus-modal-config-control {
min-width: 0;
}
.confluence-plus-modal-config-control input[type="number"],
.confluence-plus-modal-config-control select {
appearance: auto;
background: var(--confluence-plus-surface, #fff);
border: 2px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 7px;
box-sizing: border-box;
color: var(--confluence-plus-text, #172b4d);
font: inherit;
height: 40px;
outline: 0;
padding: 0 11px;
transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
width: 100%;
}
.confluence-plus-modal-config-control select {
cursor: pointer;
padding-right: 34px;
}
.confluence-plus-modal-config-control input:focus,
.confluence-plus-modal-config-control select:focus {
border-color: var(--confluence-plus-accent, #0c66e4);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 25%, transparent);
}
.confluence-plus-modal-config-control input[type="number"]::-webkit-inner-spin-button,
.confluence-plus-modal-config-control input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
.confluence-plus-modal-config-control input[type="number"] {
-moz-appearance: textfield;
}
.confluence-plus-modal-number-control {
position: relative;
}
.confluence-plus-modal-number-control input {
padding-right: 42px;
}
.confluence-plus-modal-number-control em {
color: var(--confluence-plus-muted, #5e6c84);
font-size: 12px;
font-style: normal;
position: absolute;
right: 11px;
top: 10px;
}
.confluence-plus-empty-settings {
color: var(--confluence-plus-muted, #5e6c84);
font-size: 12px;
padding: 14px 8px;
text-align: center;
}
.confluence-plus-ai-config {
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 10px;
overflow: hidden;
}
.confluence-plus-ai-config-summary {
align-items: center;
background: var(--confluence-plus-control, #f8fafc);
box-sizing: border-box;
cursor: pointer;
display: flex;
gap: 14px;
justify-content: space-between;
list-style: none;
min-height: 60px;
padding: 9px 12px;
}
.confluence-plus-ai-config-summary::-webkit-details-marker {
display: none;
}
.confluence-plus-ai-config-summary:hover {
background: var(--confluence-plus-hover, #f1f5f9);
}
.confluence-plus-ai-config-copy {
min-width: 0;
}
.confluence-plus-ai-config-summary-right {
align-items: center;
display: flex;
flex: none;
gap: 10px;
}
.confluence-plus-ai-status {
align-items: center;
border-radius: 999px;
display: inline-flex;
font-size: 11px;
font-weight: 650;
gap: 6px;
padding: 6px 9px;
white-space: nowrap;
}
.confluence-plus-ai-status::before {
border-radius: 50%;
content: "";
height: 7px;
width: 7px;
}
.confluence-plus-ai-status.ai-status-unconfigured {
background: #fff0b3;
color: #974f0c;
}
.confluence-plus-ai-status.ai-status-unconfigured::before {
background: #ff991f;
}
.confluence-plus-ai-status.ai-status-available {
background: #e3fcef;
color: #00875a;
}
.confluence-plus-ai-status.ai-status-available::before {
background: #36b37e;
}
.confluence-plus-ai-chevron {
align-items: center;
display: inline-flex;
height: 18px;
justify-content: center;
line-height: 1;
transform: translateY(0);
transition: transform .18s ease;
width: 18px;
}
.confluence-plus-ai-chevron img {
display: block;
height: 16px;
width: 16px;
}
.confluence-plus-ai-config[open] .confluence-plus-ai-chevron {
transform: rotate(180deg);
}
.confluence-plus-ai-config-content {
border-top: 1px solid var(--confluence-plus-border, #dfe1e6);
max-width: 820px;
padding: 18px 14px 14px;
}
.confluence-plus-ai-field {
margin-bottom: 14px;
}
.confluence-plus-ai-field label {
color: var(--confluence-plus-text, #172b4d);
display: block;
font-size: 12px;
font-weight: 650;
margin-bottom: 6px;
}
.confluence-plus-ai-field small {
color: var(--confluence-plus-muted, #5e6c84);
display: block;
font-size: 11px;
line-height: 1.35;
margin: -2px 0 7px;
}
.confluence-plus-ai-field input,
.confluence-plus-ai-field select {
background: var(--confluence-plus-surface, #fff);
border: 2px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 7px;
box-sizing: border-box;
color: var(--confluence-plus-text, #172b4d);
font: inherit;
font-size: 13px;
height: 40px;
outline: 0;
padding: 0 11px;
width: 100%;
}
.confluence-plus-ai-field input:focus,
.confluence-plus-ai-field select:focus {
border-color: var(--confluence-plus-accent, #0c66e4);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 25%, transparent);
}
.confluence-plus-ai-password-wrap {
position: relative;
}
.confluence-plus-ai-password-wrap input {
padding-right: 70px;
}
.confluence-plus-ai-show-key {
background: transparent;
border: 0;
color: var(--confluence-plus-accent, #0c66e4);
cursor: pointer;
font-size: 11px;
font-weight: 650;
position: absolute;
right: 10px;
top: 11px;
}
.confluence-plus-ai-actions {
align-items: center;
display: flex;
flex-wrap: nowrap;
gap: 10px;
justify-content: space-between;
max-width: 100%;
}
.confluence-plus-ai-action-buttons {
align-items: center;
display: flex;
flex: 0 0 auto;
gap: 10px;
margin-left: auto;
}
.confluence-plus-ai-actions button {
border: 0;
border-radius: 7px;
cursor: pointer;
flex: 0 0 auto;
font: inherit;
font-size: 12px;
font-weight: 650;
min-height: 38px;
padding: 0 14px;
white-space: nowrap;
width: auto;
}
.confluence-plus-ai-primary {
background: var(--confluence-plus-accent, #0c66e4);
color: #fff;
}
.confluence-plus-ai-primary:hover {
filter: brightness(.92);
}
.confluence-plus-ai-secondary {
background: var(--confluence-plus-hover, #ebecf0);
color: var(--confluence-plus-text, #172b4d);
}
.confluence-plus-ai-actions button:disabled {
cursor: wait;
opacity: .65;
}
.confluence-plus-ai-result {
color: var(--confluence-plus-muted, #5e6c84);
flex: 1 1 auto;
font-size: 11px;
line-height: 1.4;
margin: 0;
min-width: 0;
overflow-wrap: anywhere;
}
.confluence-plus-ai-action-buttons .confluence-plus-ai-primary {
min-width: 112px;
}
.confluence-plus-ai-tooltip-host {
display: inline-flex;
position: relative;
}
.confluence-plus-ai-tooltip {
background: var(--confluence-plus-text, #172b4d);
border: 1px solid color-mix(in srgb, var(--confluence-plus-surface, #fff) 18%, transparent);
border-radius: 8px;
bottom: calc(100% + 10px);
box-shadow: 0 8px 22px rgba(0, 0, 0, .28);
color: var(--confluence-plus-surface, #fff);
font-size: 12px;
font-weight: 500;
left: 50%;
line-height: 1.45;
max-width: 280px;
opacity: 0;
padding: 9px 12px;
pointer-events: none;
position: absolute;
text-align: left;
transform: translate(-50%, 4px);
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
visibility: hidden;
white-space: normal;
width: max-content;
z-index: 10004;
}
.confluence-plus-ai-tooltip::after {
border: 5px solid transparent;
border-top-color: var(--confluence-plus-text, #172b4d);
content: "";
left: 50%;
position: absolute;
top: 100%;
transform: translateX(-50%);
}
.confluence-plus-ai-tooltip-host:hover .confluence-plus-ai-tooltip,
.confluence-plus-ai-tooltip-host:focus-within .confluence-plus-ai-tooltip {
opacity: 1;
transform: translate(-50%, 0);
visibility: visible;
}
.confluence-plus-ai-result.ai-result-success {
color: #00875a;
}
.confluence-plus-ai-result.ai-result-error {
color: #de350b;
}
.confluence-plus-theme-dark .confluence-plus-ai-chevron img {
filter: brightness(0) invert(1);
}
@media (max-width: 700px) {
.confluence-plus-ai-actions {
align-items: stretch;
flex-wrap: wrap;
}
.confluence-plus-ai-action-buttons {
margin-left: auto;
}
.confluence-plus-ai-result {
flex-basis: 100%;
margin-top: 2px;
}
}
.confluence-plus-theme {
--confluence-plus-bg: #fff;
--confluence-plus-surface: #fff;
--confluence-plus-text: #172b4d;
--confluence-plus-muted: #5e6c84;
--confluence-plus-border: #dfe1e6;
--confluence-plus-control: #f4f5f7;
--confluence-plus-accent: #0c66e4;
--confluence-plus-hover: #ebecf0;
--confluence-plus-backdrop: rgba(9, 30, 66, .32);
color-scheme: light;
}
.confluence-plus-theme-dark {
--confluence-plus-bg: #101214;
--confluence-plus-surface: #1d2125;
--confluence-plus-text: #e6edf3;
--confluence-plus-muted: #a8b3c2;
--confluence-plus-border: #3b434d;
--confluence-plus-control: #252b33;
--confluence-plus-accent: #579dff;
--confluence-plus-hover: #30363d;
--confluence-plus-backdrop: rgba(0, 0, 0, .58);
color-scheme: dark;
}
.confluence-plus-theme .confluence-plus-ado-modal-backdrop {
background: var(--confluence-plus-backdrop);
}
.confluence-plus-theme .confluence-plus-ado-modal-panel {
background: var(--confluence-plus-surface);
color: var(--confluence-plus-text);
box-shadow: 0 18px 48px var(--confluence-plus-backdrop);
}
.confluence-plus-theme .confluence-plus-ado-modal-header {
border-color: var(--confluence-plus-border);
}
.confluence-plus-theme .confluence-plus-ado-modal-header small,
.confluence-plus-theme .confluence-plus-modal-config-description,
.confluence-plus-theme .confluence-plus-modal-number-control em {
color: var(--confluence-plus-muted);
}
.confluence-plus-theme .confluence-plus-modal-config-row {
background: var(--confluence-plus-control);
border-color: var(--confluence-plus-border);
}
.confluence-plus-theme .confluence-plus-modal-config-title,
.confluence-plus-theme .confluence-plus-modal-config-summary,
.confluence-plus-theme .confluence-plus-modal-config-section {
color: var(--confluence-plus-text);
}
.confluence-plus-theme .confluence-plus-ado-modal-close {
color: var(--confluence-plus-muted);
}
.confluence-plus-theme .confluence-plus-ado-modal-close:hover,
.confluence-plus-theme .confluence-plus-modal-config-control input,
.confluence-plus-theme .confluence-plus-modal-config-control select {
background: var(--confluence-plus-surface);
border-color: var(--confluence-plus-border);
color: var(--confluence-plus-text);
}
.confluence-plus-theme .confluence-plus-ado-modal-close:hover {
background: var(--confluence-plus-hover);
}
.confluence-plus-theme .confluence-plus-modal-config-control input:focus,
.confluence-plus-theme .confluence-plus-modal-config-control select:focus {
border-color: var(--confluence-plus-accent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent) 25%, transparent);
}
.confluence-plus-theme .confluence-plus-modal-config-chevron img {
filter: none;
}
.confluence-plus-theme-dark .confluence-plus-modal-config-chevron img {
filter: brightness(0) invert(1);
}
.confluence-plus-theme .confluence-plus-empty-settings {
color: var(--confluence-plus-muted);
}
@media (max-width: 620px) {
.confluence-plus-ado-modal-panel {
height: calc(100dvh - 16px);
max-width: calc(100vw - 16px);
}
.confluence-plus-modal-config-row {
align-items: stretch;
flex-direction: column;
}
.confluence-plus-modal-config-control {
width: 100%;
}
.confluence-plus-modal-config-control select,
.confluence-plus-modal-number-control input {
width: 100%;
}
}
#confluence-plus-root {
position: fixed;
z-index: 100000;
font-size: 14px;
color: var(--confluence-plus-text, #333);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-inline-feature-content,
#confluence-plus-root.confluence-plus-theme .confluence-plus-modal-status,
#confluence-plus-root.confluence-plus-theme .confluence-plus-title {
color: var(--confluence-plus-text);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-modal-hint {
color: var(--confluence-plus-muted);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-label-manual-input,
#confluence-plus-root.confluence-plus-theme .confluence-plus-label-edit {
background: var(--confluence-plus-surface);
border-color: var(--confluence-plus-border);
color: var(--confluence-plus-text);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-secondary-btn {
background: var(--confluence-plus-surface);
border-color: var(--confluence-plus-accent);
color: var(--confluence-plus-accent);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-popconfirm {
background: var(--confluence-plus-surface);
border-color: var(--confluence-plus-border);
color: var(--confluence-plus-text);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-popconfirm-message {
color: var(--confluence-plus-muted);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-popconfirm::after {
background: var(--confluence-plus-surface);
border-color: var(--confluence-plus-border);
}
#confluence-plus-root.confluence-plus-theme .confluence-plus-confirm-cancel {
background: var(--confluence-plus-hover);
color: var(--confluence-plus-text);
}
.confluence-plus-context-menu {
position: fixed;
/* Keep the menu above the expanded root (z-index: 999999), matching
the layering used by the pre-refactor UI. */
z-index: 1000003;
width: 220px;
box-sizing: border-box;
border: 1px solid #dfe1e6;
border-radius: 6px;
background: white;
box-shadow: 0 8px 24px rgba(9, 30, 66, .24);
padding: 4px 0;
color: #172b4d;
font-size: 12px;
}
.confluence-plus-context-menu-item,
.confluence-plus-context-menu-action {
display: block;
width: 100%;
box-sizing: border-box;
border: none;
background: transparent;
padding: 9px 12px;
text-align: left;
white-space: nowrap;
user-select: none;
}
.confluence-plus-context-menu-action {
color: #172b4d;
cursor: pointer;
font: inherit;
}
.confluence-plus-context-menu-action:hover {
background: #deebff;
color: #0747a6;
}
.confluence-plus-context-menu-info {
color: #8993a4;
cursor: default;
}
#confluence-plus-root.confluence-plus-content-window.confluence-plus-expanded {
background: var(--confluence-plus-surface, #fff);
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 12px;
box-shadow: 0 18px 48px rgba(9, 30, 66, .22);
color: var(--confluence-plus-text, #172b4d);
overflow: hidden;
box-sizing: border-box;
resize: both;
min-width: min(720px, calc(100vw - 20px));
min-height: min(520px, calc(100vh - 20px));
max-width: 100vw;
max-height: 100vh;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-header {
align-items: center;
background: #172b4d;
color: #fff;
display: flex;
flex: none;
gap: 12px;
height: 62px;
min-height: 62px;
padding: 0 16px;
user-select: none;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-mark {
align-items: center;
background: #0c66e4;
border-radius: 8px;
display: block;
flex: none;
height: 34px;
object-fit: cover;
width: 38px;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy {
min-width: 0;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy strong,
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy small {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy strong {
font-size: 16px;
line-height: 1.2;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy small {
color: #c7d3e3;
font-size: 11px;
margin-top: 2px;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-spacer {
flex: 1;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-header-actions {
flex: none;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-action {
background: transparent;
border: 0;
border-radius: 6px;
color: #dbe7f5;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
line-height: 1;
padding: 0;
font-size: 18px;
height: 32px;
width: 34px;
}
.confluence-plus-header-actions button svg {
width: 18px;
height: 18px;
display: block;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-action:hover {
background: rgba(255, 255, 255, .14);
color: #fff;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-close {
font-size: 24px;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-body {
display: flex;
flex: 1;
height: calc(100% - 62px);
min-height: 0;
position: relative;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-scroll {
display: flex;
flex: 1;
flex-direction: column;
gap: 14px;
min-height: 0;
overflow: auto;
padding: 16px 18px 22px;
scrollbar-color: var(--confluence-plus-border, #dfe1e6) transparent;
scrollbar-width: thin;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-scroll::-webkit-scrollbar {
width: 8px;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-scroll::-webkit-scrollbar-thumb {
background: var(--confluence-plus-border, #dfe1e6);
border-radius: 999px;
}
.confluence-plus-content-feature-list {
display: grid;
gap: 12px;
}
.confluence-plus-content-feature {
background: var(--confluence-plus-surface, #fff);
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 10px;
overflow: hidden;
}
.confluence-plus-content-feature-summary {
align-items: center;
background: var(--confluence-plus-control, #f8fafc);
cursor: pointer;
display: flex;
gap: 14px;
justify-content: space-between;
list-style: none;
min-height: 60px;
padding: 9px 12px;
}
.confluence-plus-content-feature-summary::-webkit-details-marker {
display: none;
}
.confluence-plus-content-feature-summary:hover {
background: var(--confluence-plus-hover, #f1f5f9);
}
.confluence-plus-content-feature-copy {
min-width: 0;
}
.confluence-plus-content-feature-copy strong,
.confluence-plus-content-feature-copy small {
display: block;
}
.confluence-plus-content-feature-copy strong {
color: var(--confluence-plus-text, #172b4d);
font-size: 13px;
font-weight: 650;
}
.confluence-plus-content-feature-copy small {
color: var(--confluence-plus-muted, #5e6c84);
font-size: 11px;
line-height: 1.35;
margin-top: 4px;
}
.confluence-plus-content-feature-ai-warning {
color: #de350b;
font-weight: 700;
margin-right: 4px;
}
.confluence-plus-content-feature-chevron {
align-items: center;
display: inline-flex;
flex: none;
height: 20px;
justify-content: center;
transition: transform .18s ease;
width: 20px;
}
.confluence-plus-content-feature-chevron img {
display: block;
height: 18px;
width: 18px;
}
.confluence-plus-content-feature[open] .confluence-plus-content-feature-chevron {
transform: rotate(180deg);
}
.confluence-plus-content-feature-body {
border-top: 1px solid var(--confluence-plus-border, #dfe1e6);
padding: 0;
}
.confluence-plus-feature-inline-host {
min-width: 0;
}
.confluence-plus-inline-feature-content {
box-sizing: border-box;
color: var(--confluence-plus-text, #172b4d);
min-width: 0;
padding: 12px;
}
.confluence-plus-feature-inline-host .confluence-plus-space-tree {
max-height: 360px;
}
.confluence-plus-inline-feature-error {
color: #de350b;
font-size: 12px;
line-height: 1.45;
padding: 12px;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-feature-summary:focus-visible {
outline: 2px solid var(--confluence-plus-accent, #0c66e4);
outline-offset: 2px;
}
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-feature-chevron img {
filter: none;
}
#confluence-plus-root.confluence-plus-content-window.confluence-plus-theme-dark .confluence-plus-content-feature-chevron img {
filter: brightness(0) invert(1);
}
@media (max-width: 720px) {
#confluence-plus-root.confluence-plus-content-window.confluence-plus-expanded {
min-width: 0;
width: calc(100vw - 16px) !important;
}
}
#confluence-plus-root.confluence-plus-expanded {
background: white;
border: 1px solid #ddd;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, .15);
overflow: hidden;
box-sizing: border-box;
resize: both;
min-width: ${config.ui.minWidth}px;
min-height: ${config.ui.minHeight}px;
max-width: 100vw;
max-height: 100vh;
}
.confluence-plus-header {
height: 48px;
padding: 0 16px;
background: #0052cc;
color: white;
display: flex;
align-items: center;
justify-content: space-between;
cursor: move;
user-select: none;
}
.confluence-plus-header-title {
flex: 0 0 auto;
min-width: 0;
}
.confluence-plus-session-label {
margin-top: 2px;
font-size: 11px;
opacity: .8;
}
.confluence-plus-header-actions {
display: flex;
align-items: center;
gap: 6px;
}
.confluence-plus-header button {
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: rgba(255, 255, 255, .2);
color: white;
font-size: 18px;
line-height: 1;
cursor: pointer;
}
.confluence-plus-header-icon-btn {
font-size: 15px !important;
}
.confluence-plus-section {
margin-bottom: 20px;
}
.confluence-plus-title {
margin-bottom: 12px;
font-weight: bold;
}
.confluence-plus-tree-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
}
.confluence-plus-tree-refresh {
width: 28px;
height: 28px;
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 6px;
background: var(--confluence-plus-surface, #fff);
color: var(--confluence-plus-text, #172b4d);
cursor: pointer;
font-size: 17px;
}
.confluence-plus-tree-refresh:hover {
background: var(--confluence-plus-hover, #ebecf0);
}
.confluence-plus-space-tree {
max-height: calc(100% - 48px);
overflow: auto;
padding: 4px 0;
}
.confluence-plus-tree-list {
margin: 0;
padding-left: 0;
list-style: none;
}
.confluence-plus-tree-item .confluence-plus-tree-list {
margin-left: 18px;
}
.confluence-plus-tree-row {
position: relative;
display: flex;
align-items: center;
gap: 6px;
min-height: 32px;
border-radius: 6px;
padding: 3px 6px;
}
.confluence-plus-tree-row:hover {
background: var(--confluence-plus-hover, #f4f5f7);
}
.confluence-plus-tree-current {
background: color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 16%, var(--confluence-plus-surface, #fff));
color: var(--confluence-plus-accent, #0747a6);
font-weight: 600;
}
.confluence-plus-tree-toggle,
.confluence-plus-tree-toggle-spacer {
width: 22px;
height: 24px;
flex: 0 0 22px;
}
.confluence-plus-tree-toggle {
border: none;
border-radius: 4px;
background: transparent;
color: var(--confluence-plus-muted, #42526e);
cursor: pointer;
font-size: 15px;
line-height: 1;
}
.confluence-plus-tree-toggle:hover {
background: var(--confluence-plus-hover, #dfe1e6);
}
.confluence-plus-tree-title-wrap {
min-width: 0;
flex: 1;
display: flex;
align-items: baseline;
overflow: hidden;
}
.confluence-plus-tree-title {
min-width: 0;
flex: 0 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.confluence-plus-tree-child-count {
flex: 0 0 auto;
margin-left: 4px;
color: var(--confluence-plus-accent, #0052cc);
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.confluence-plus-tree-current-badge {
flex: 0 0 auto;
margin-left: 4px;
border-radius: 999px;
background: var(--confluence-plus-accent, #0052cc);
color: var(--confluence-plus-surface, #fff);
padding: 2px 6px;
font-size: 10px;
white-space: nowrap;
}
.confluence-plus-label-toolbar {
align-items: end;
background: var(--confluence-plus-control, #f7f9fb);
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 9px;
display: grid;
gap: 14px;
grid-template-columns: minmax(0, 1fr) auto;
padding: 10px 12px;
}
.confluence-plus-label-toolbar-copy {
min-width: 0;
}
.confluence-plus-label-toolbar-copy .confluence-plus-title {
color: var(--confluence-plus-text, #172b4d);
margin: 0;
}
.confluence-plus-label-toolbar-copy .confluence-plus-modal-hint {
margin-top: 4px;
}
.confluence-plus-label-generate-button,
.confluence-plus-label-apply-button {
align-self: end;
background: var(--confluence-plus-accent, #0c66e4);
border: 0;
border-radius: 7px;
color: #fff;
cursor: pointer;
font: inherit;
font-size: 12px;
font-weight: 650;
height: 40px;
padding: 0 14px;
white-space: nowrap;
}
.confluence-plus-label-generate-button {
min-width: 190px;
}
.confluence-plus-label-generate-button:hover:not(:disabled),
.confluence-plus-label-apply-button:hover:not(:disabled) {
filter: brightness(.92);
}
.confluence-plus-label-generate-button:disabled,
.confluence-plus-label-apply-button:disabled {
cursor: wait;
opacity: .6;
}
.confluence-plus-label-results-header {
color: var(--confluence-plus-text, #172b4d);
font-size: 13px;
font-weight: 700;
margin: 14px 0 8px;
max-width: 100%;
}
.confluence-plus-label-results-header-text {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.confluence-plus-label-title-tooltip {
position: relative;
}
.confluence-plus-label-title-tooltip::after {
background: var(--confluence-plus-text, #172b4d);
border: 1px solid color-mix(in srgb, var(--confluence-plus-surface, #fff) 18%, transparent);
border-radius: 7px;
bottom: calc(100% + 8px);
box-shadow: 0 8px 22px rgba(0, 0, 0, .28);
color: var(--confluence-plus-surface, #fff);
content: attr(data-confluence-plus-tooltip);
font-size: 11px;
font-weight: 500;
left: 0;
line-height: 1.4;
max-width: min(420px, calc(100vw - 40px));
opacity: 0;
padding: 8px 10px;
pointer-events: none;
position: absolute;
transform: translateY(4px);
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;
visibility: hidden;
white-space: normal;
width: max-content;
z-index: 10004;
}
.confluence-plus-label-title-tooltip:hover::after,
.confluence-plus-label-title-tooltip:focus-visible::after {
opacity: 1;
transform: translateY(0);
visibility: visible;
}
.confluence-plus-label-list {
display: grid;
gap: 10px;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
margin-bottom: 12px;
}
.confluence-plus-label-item {
align-items: center;
background: var(--confluence-plus-control, #f7f9fb);
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 8px;
display: grid;
gap: 8px;
grid-template-columns: 18px minmax(0, 1fr) 28px;
min-height: 42px;
padding: 7px 8px;
}
.confluence-plus-label-item input[type="checkbox"] {
accent-color: var(--confluence-plus-accent, #0c66e4);
height: 16px;
width: 16px;
}
.confluence-plus-label-edit,
.confluence-plus-label-manual-input {
background: var(--confluence-plus-surface, #fff);
border: 1px solid var(--confluence-plus-border, #dfe1e6);
border-radius: 6px;
box-sizing: border-box;
color: var(--confluence-plus-text, #172b4d);
font: inherit;
height: 32px;
min-width: 0;
padding: 0 9px;
width: 100%;
}
.confluence-plus-label-edit:focus,
.confluence-plus-label-manual-input:focus {
border-color: var(--confluence-plus-accent, #0c66e4);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 25%, transparent);
outline: 0;
}
.confluence-plus-label-remove {
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: #ffebe6;
color: #bf2600;
cursor: pointer;
font-size: 16px;
}
.confluence-plus-label-add-row {
display: grid;
gap: 8px;
grid-template-columns: minmax(0, 1fr) auto;
margin-bottom: 12px;
}
.confluence-plus-label-apply-row {
display: flex;
justify-content: flex-end;
margin-top: 12px;
}
.confluence-plus-label-apply-tooltip-host {
display: inline-flex;
position: relative;
}
.confluence-plus-label-apply-tooltip-host::after {
background: var(--confluence-plus-text, #172b4d);
border: 1px solid color-mix(in srgb, var(--confluence-plus-surface, #fff) 18%, transparent);
border-radius: 7px;
bottom: calc(100% + 9px);
color: #fff;
content: attr(data-tooltip);
font-size: 11px;
line-height: 1.35;
max-width: 260px;
opacity: 0;
padding: 7px 9px;
pointer-events: none;
position: absolute;
right: 0;
transform: translateY(4px);
transition: opacity .15s ease, transform .15s ease;
visibility: hidden;
white-space: normal;
width: max-content;
z-index: 100006;
}
.confluence-plus-label-apply-tooltip-host:hover::after,
.confluence-plus-label-apply-tooltip-host:focus-within::after,
.confluence-plus-label-apply-tooltip-host:focus::after {
opacity: 1;
transform: translateY(0);
visibility: visible;
}
#confluence-plus-root.confluence-plus-theme-dark .confluence-plus-label-apply-tooltip-host::after {
background: #172b4d;
border-color: #4c9aff;
color: #fff;
}
@media (max-width: 700px) {
.confluence-plus-label-toolbar {
grid-template-columns: 1fr;
}
.confluence-plus-label-generate-button {
width: 100%;
}
.confluence-plus-label-apply-row {
justify-content: stretch;
}
.confluence-plus-label-apply-button {
width: 100%;
}
}
.confluence-plus-popconfirm-wrap {
position: fixed;
z-index: 2147483001;
width: 300px;
}
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm {
background: #172b4d;
border-color: #4c9aff;
color: #fff;
}
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm::after {
background: #172b4d;
border-color: #4c9aff;
}
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm-title {
color: #fff;
}
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm-message {
color: #c7d3e3;
}
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-confirm-cancel {
background: #253858;
color: #fff;
}
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-confirm-ok {
background: #4c9aff;
color: #172b4d;
}
.confluence-plus-popconfirm {
position: relative;
border: 1px solid #dfe1e6;
border-radius: 8px;
background: white;
box-shadow: 0 8px 24px rgba(9, 30, 66, .22);
padding: 14px;
}
.confluence-plus-popconfirm::after {
content: '';
position: absolute;
left: 24px;
bottom: -7px;
width: 12px;
height: 12px;
border-right: 1px solid #dfe1e6;
border-bottom: 1px solid #dfe1e6;
background: white;
transform: rotate(45deg);
}
.confluence-plus-popconfirm-title {
color: #172b4d;
font-size: 14px;
font-weight: 700;
}
.confluence-plus-popconfirm-message {
margin-top: 7px;
color: #42526e;
font-size: 12px;
line-height: 1.45;
word-break: break-word;
}
.confluence-plus-popconfirm-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 12px;
}
.confluence-plus-popconfirm-actions button {
border: none;
border-radius: 6px;
padding: 6px 10px;
cursor: pointer;
font-size: 12px;
}
.confluence-plus-confirm-cancel {
background: #f1f2f4;
color: #172b4d;
}
.confluence-plus-confirm-ok {
background: #0052cc;
color: white;
}
.confluence-plus-label-add-row .confluence-plus-secondary-btn {
width: auto;
}
.confluence-plus-feature-btn {
margin-bottom: 10px;
}
.confluence-plus-secondary-btn {
width: 100%;
border: 1px solid #0052cc;
border-radius: 8px;
background: white;
color: #0052cc;
padding: 9px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
text-align: left;
}
.confluence-plus-status-badge {
display: inline-block;
border-radius: 999px;
padding: 3px 7px;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
}
.confluence-plus-status-badge.not-configured {
background: #f1f2f4;
color: #6b778c;
}
.confluence-plus-status-badge.unverified {
background: #fff0b3;
color: #7f5f01;
}
.confluence-plus-status-badge.available {
background: #abf5d1;
color: #006644;
}
.confluence-plus-status-badge.unavailable {
background: #ffbdad;
color: #bf2600;
}
.confluence-plus-modal-hint {
margin-top: 6px;
color: var(--confluence-plus-muted, #6b778c);
font-size: 11px;
line-height: 1.4;
}
.confluence-plus-modal-status {
min-height: 18px;
margin-top: 10px;
font-size: 12px;
white-space: pre-wrap;
}
.confluence-plus-modal-status.success {
color: #16803c;
}
.confluence-plus-modal-status.error {
color: #d93025;
}
.confluence-plus-toast {
position: fixed;
right: 20px;
bottom: 20px;
z-index: 100006;
border-radius: 8px;
background: #333;
color: white;
padding: 10px 14px;
}
.confluence-plus-toast.error {
background: #d93025;
}
.confluence-plus-toast-stack { display: flex; flex-direction: column; gap: 10px; max-width: min(430px, calc(100vw - 32px)); pointer-events: none; position: fixed; right: 20px; top: 20px; width: min(430px, calc(100vw - 32px)); z-index: 100006; }
.confluence-plus-toast-stack[hidden] { display: none; }
.confluence-plus-toast-stack .confluence-plus-toast { align-items: flex-start; background: #fff; border: 1px solid #b36b00; border-radius: 10px; bottom: auto; box-shadow: 0 16px 42px rgba(9,30,66,.28); color: #172b4d; display: flex; gap: 12px; max-width: none; opacity: 0; padding: 14px 16px 30px; pointer-events: auto; position: relative; right: auto; top: auto; transform: translateX(18px); transition: opacity .2s ease, transform .2s ease; visibility: hidden; width: 100%; }
.confluence-plus-toast-stack .confluence-plus-toast[hidden] { display: none; }
.confluence-plus-toast-stack .confluence-plus-toast.is-visible { opacity: 1; transform: translateX(0); visibility: visible; }
.confluence-plus-toast-stack .confluence-plus-toast.is-leaving { opacity: 0; transform: translateX(18px); }
.confluence-plus-toast-icon { align-items: center; background: #b36b00; border-radius: 50%; color: #fff; display: inline-flex; flex: 0 0 24px; font-size: 14px; font-weight: 800; height: 24px; justify-content: center; line-height: 1; width: 24px; }
.confluence-plus-toast-copy { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }
.confluence-plus-toast-copy strong { font-size: 13px; }
.confluence-plus-toast-copy span, .confluence-plus-toast-close { color: #5e6c84; }
.confluence-plus-toast-copy span { font-size: 12px; line-height: 1.45; overflow-wrap: anywhere; }
.confluence-plus-toast-copy small { bottom: 8px; color: #5e6c84; font-size: 10px; line-height: 1; position: absolute; right: 12px; }
.confluence-plus-toast-close { background: transparent; border: 0; border-radius: 5px; cursor: pointer; flex: 0 0 auto; font-size: 20px; height: 24px; line-height: 1; padding: 0 4px; }
.confluence-plus-toast-close:hover { background: #ebecf0; color: #172b4d; }
.confluence-plus-toast-close:focus-visible { outline: 2px solid #0c66e4; outline-offset: 2px; }
.confluence-plus-toast-stack.is-dark .confluence-plus-toast { background: #172b4d; border-color: #4c9aff; color: #fff; }
.confluence-plus-toast-stack.is-dark .confluence-plus-toast-copy span, .confluence-plus-toast-stack.is-dark .confluence-plus-toast-close, .confluence-plus-toast-stack.is-dark .confluence-plus-toast-copy small { color: #c7d3e3; }
`);
}
// src/plugins/confluence/entry.js
installStyles({ config: Config });
installSharedTooltips();
bootstrap();
})();