// ==UserScript==
// @name Confluence Plus
// @namespace https://docs.scriptcat.org/
// @version 2.0.2
// @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: {},
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: "Enter the Azure resource endpoint; the /openai/v1 path is added automatically.",
apiKey: "API Key",
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.",
aiPoweredLabel: "AI-Powered:",
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.",
endpointMustNotContainCredentials: "Endpoint must not contain credentials, query parameters, or a hash.",
endpointRequired: "Enter an Azure OpenAI endpoint.",
apiKeyRequired: "Enter an Azure OpenAI API key.",
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: "\u8F93\u5165 Azure resource endpoint\uFF0C\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u6DFB\u52A0 /openai/v1 \u8DEF\u5F84\u3002",
apiKey: "API Key",
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",
aiPoweredLabel: "AI \u9A71\u52A8\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",
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",
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({ Storage: Storage2, getLocaleText: getLocaleText2 }) {
const AIConfig2 = (() => {
const apiProtocol = "azure-openai-v1";
const defaults = {
schemaVersion: 2,
endpoint: "",
apiKey: "",
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));
const hadLegacyApiVersions = stored && (Object.prototype.hasOwnProperty.call(stored, "apiVersion") || Object.prototype.hasOwnProperty.call(stored, "modelsApiVersion"));
delete config.apiVersion;
delete config.modelsApiVersion;
const staleHealth = Boolean(config.lastCheck && config.lastCheck.apiProtocol !== apiProtocol);
if (staleHealth) config.lastCheck = null;
if (stored?.schemaVersion !== defaults.schemaVersion || hadLegacyApiVersions || staleHealth) {
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 (url.username || url.password || url.search || url.hash) {
throw new Error(getLocaleText2("endpointMustNotContainCredentials"));
}
const openAiPathIndex = url.pathname.toLowerCase().indexOf("/openai");
if (openAiPathIndex >= 0) url.pathname = url.pathname.slice(0, openAiPathIndex);
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.deployment = next.deployment.trim();
next.tokenParameter = ["max_tokens", "max_completion_tokens"].includes(next.tokenParameter) ? next.tokenParameter : defaults.tokenParameter;
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.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(),
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.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,
apiProtocol,
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.apiProtocol !== apiProtocol || 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 = {
getUrl(endpoint, path) {
return `${endpoint}/openai/v1${path}`;
},
getHeaders(apiKey) {
return {
"api-key": apiKey,
Accept: "application/json"
};
},
async verifyDeployment(config) {
const response = await Http2.post(
this.getChatCompletionsUrl(config),
{
model: config.deployment,
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) {
return this.getUrl(config.endpoint, "/chat/completions");
},
getResponsesUrl(config) {
return this.getUrl(config.endpoint, "/responses");
},
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),
{
model: config.deployment,
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/floating-window.js
function readGeometry(element) {
const rect = element.getBoundingClientRect();
return { left: rect.left, top: rect.top, width: rect.width, height: rect.height };
}
function isViewportGeometry(rect, view) {
return Math.abs(rect.left) <= 1 && Math.abs(rect.top) <= 1 && Math.abs(rect.width - view.innerWidth) <= 1 && Math.abs(rect.height - view.innerHeight) <= 1;
}
function createFloatingWindowController({
root,
headerSelector,
maximizeButtonSelector,
closeButtonSelector,
getState,
setState,
applyGeometry,
onStateChange = () => {
},
onClose,
onViewportResize,
isActive = () => true,
interactiveSelector = "button, input, select, textarea, a"
}) {
const document2 = root.ownerDocument;
const view = document2.defaultView;
let normalGeometry = getState().geometry || null;
let drag = null;
let previousUserSelect = "";
const applyFullscreenGeometry = () => {
const geometry = normalGeometry || getState().geometry || readGeometry(root);
setState({ maximized: true, geometry });
applyGeometry({ maximized: true, geometry });
onStateChange(true, geometry);
};
const restoreGeometry = (geometry = normalGeometry || getState().geometry) => {
setState({ maximized: false, geometry });
if (geometry) applyGeometry({ maximized: false, geometry });
onStateChange(false, geometry);
};
const toggleMaximized = () => {
if (getState().maximized) {
restoreGeometry();
return;
}
const measured = readGeometry(root);
normalGeometry = measured.width && measured.height ? measured : normalGeometry || getState().geometry;
applyFullscreenGeometry();
};
const handleClick = (event) => {
const target = event.target;
if (!target?.closest) return;
if (target.closest(maximizeButtonSelector)) {
event.preventDefault();
event.stopPropagation();
toggleMaximized();
} else if (target.closest(closeButtonSelector)) {
event.preventDefault();
event.stopPropagation();
onClose();
}
};
const handleDoubleClick = (event) => {
const target = event.target;
if (!target?.closest?.(headerSelector) || target.closest(interactiveSelector)) return;
event.preventDefault();
event.stopPropagation();
toggleMaximized();
};
const handleMouseDown = (event) => {
const target = event.target;
if (event.button !== 0 || getState().maximized || !target?.closest?.(headerSelector) || target.closest(interactiveSelector)) return;
const rect = root.getBoundingClientRect();
drag = { offsetX: event.clientX - rect.left, offsetY: event.clientY - rect.top };
previousUserSelect = document2.body.style.userSelect;
document2.body.style.userSelect = "none";
};
const handleMouseMove = (event) => {
if (!drag || getState().maximized) return;
const rect = root.getBoundingClientRect();
const left = Math.max(0, Math.min(Math.max(0, view.innerWidth - rect.width), event.clientX - drag.offsetX));
const top = Math.max(0, Math.min(Math.max(0, view.innerHeight - rect.height), event.clientY - drag.offsetY));
normalGeometry = { left, top, width: rect.width, height: rect.height };
setState({ geometry: normalGeometry });
applyGeometry({ maximized: false, geometry: normalGeometry });
};
const handleMouseUp = () => {
if (!drag) return;
drag = null;
document2.body.style.userSelect = previousUserSelect;
};
const handleResize = () => {
if (!isActive()) return;
const rect = readGeometry(root);
const state = getState();
const isFullscreen = isViewportGeometry(rect, view);
if (state.maximized && !isFullscreen) {
normalGeometry = rect;
restoreGeometry(rect);
} else if (!state.maximized && isFullscreen) {
normalGeometry = normalGeometry || state.geometry || rect;
applyFullscreenGeometry();
} else if (!state.maximized) {
normalGeometry = rect;
setState({ geometry: rect });
}
};
const handleViewportResize = () => {
if (!isActive()) return;
if (getState().maximized) {
applyGeometry({ maximized: true, geometry: normalGeometry || getState().geometry });
return;
}
if (onViewportResize) {
onViewportResize();
} else {
const rect = readGeometry(root);
const left = Math.max(0, Math.min(Math.max(0, view.innerWidth - rect.width), rect.left));
const top = Math.max(0, Math.min(Math.max(0, view.innerHeight - rect.height), rect.top));
if (left !== rect.left || top !== rect.top) {
normalGeometry = { ...rect, left, top };
setState({ geometry: normalGeometry });
applyGeometry({ maximized: false, geometry: normalGeometry });
}
}
if (isActive() && !getState().maximized) {
normalGeometry = readGeometry(root);
setState({ geometry: normalGeometry });
}
};
root.addEventListener("click", handleClick, true);
root.addEventListener("dblclick", handleDoubleClick);
root.addEventListener("mousedown", handleMouseDown);
document2.addEventListener("mousemove", handleMouseMove);
document2.addEventListener("mouseup", handleMouseUp);
view.addEventListener("resize", handleViewportResize);
const ResizeObserver = view.ResizeObserver;
const resizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(handleResize) : null;
resizeObserver?.observe(root);
return {
destroy() {
root.removeEventListener("click", handleClick, true);
root.removeEventListener("dblclick", handleDoubleClick);
root.removeEventListener("mousedown", handleMouseDown);
document2.removeEventListener("mousemove", handleMouseMove);
document2.removeEventListener("mouseup", handleMouseUp);
view.removeEventListener("resize", handleViewportResize);
resizeObserver?.disconnect();
handleMouseUp();
}
};
}
// src/shared/toast.js
function createToastController({ selector, className, getLocaleText: getLocaleText2, closeLabelKey, getThemeClass, maxVisible = 3 }) {
const timers = /* @__PURE__ */ new WeakMap();
const findStack = () => {
let stack = $(selector);
if (!stack.length) stack = $(`[dft-data-toast-stack].${className}-stack`);
return stack;
};
const ensureStack = () => {
let stack = findStack();
if (stack.length) return stack;
stack = $(`
`).appendTo(document.body);
return stack;
};
const clearToastTimer = (toast) => {
const timer = timers.get(toast[0]);
if (timer === void 0) return;
window.clearTimeout(timer);
timers.delete(toast[0]);
};
const hideItem = (toast) => {
clearToastTimer(toast);
const stack = toast.parent();
toast.removeClass("is-visible is-success is-info is-error").addClass("is-leaving");
window.setTimeout(() => {
toast.remove();
if (!stack.children("[dft-data-toast-item]").length) stack.prop("hidden", true);
}, 180);
};
const controller = {
hide() {
const stack = findStack();
const items = stack.children("[dft-data-toast-item]");
items.each(function() {
clearToastTimer($(this));
});
items.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("[dft-data-toast-title]").text(title || "");
toast.find("[dft-data-toast-message]").text(message || "");
toast.find("[dft-data-toast-time]").text((/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }));
toast.find("[dft-data-toast-icon]").text(normalized === "success" ? "\u2713" : normalized === "error" ? "!" : "i");
toast.find("[dft-data-toast-close]").attr("aria-label", getLocaleText2(closeLabelKey));
toast.on("click.toast", "[dft-data-toast-close]", () => hideItem(toast));
stack.prop("hidden", false).append(toast);
const items = stack.children("[dft-data-toast-item]");
if (items.length > maxVisible) {
const expired = items.slice(0, items.length - maxVisible);
expired.each(function() {
clearToastTimer($(this));
});
expired.remove();
}
requestAnimationFrame(() => toast.addClass("is-visible"));
timers.set(toast[0], 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({ selector: "[data-confluence-toast-stack]", className: "confluence-plus-toast", getLocaleText: getLocaleText2, getThemeClass: () => root?.classList.contains("confluence-plus-theme-dark"), closeLabelKey: "close" });
let root;
let windowController;
let unsubscribe;
let overlay;
let contextMenu;
let adoStyleFab;
let adoStyleConfig;
let themeMediaQuery;
let themeChangeHandler;
let unsubscribeThemeChange;
let contentFeatureUi = {
spaceTreeOpen: false,
aiLabelsOpen: false
};
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 updateWindowButton() {
if (!root) return;
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 updateWindowChrome() {
if (!root) return;
applyLayout();
updateWindowButton();
}
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 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;
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,
deployment: deployment.value,
tokenParameter: tokenParameter.value
});
refreshContentAiSummary();
};
[endpoint, apiKey, 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 = "";
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
});
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("aiPoweredLabel")}
${getLocaleText2("generateLabelDescription")}
`;
applyContentWindowSpaceIcon();
root.querySelector("#confluence-plus-close-btn").innerHTML = windowIcons2.close;
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();
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 init() {
createAdoStyleEntry();
root = document.createElement("div");
root.id = "confluence-plus-root";
root.addEventListener("contextmenu", showContextMenu);
document.body.appendChild(root);
windowController = createFloatingWindowController({
root,
headerSelector: ".confluence-plus-header",
maximizeButtonSelector: "#confluence-plus-toggle-maximize",
closeButtonSelector: "#confluence-plus-close-btn",
getState: () => {
const ui = Store2.getUi();
const geometry = ui.maximized && ui._normalWidth != null ? { left: ui._normalX ?? ui.x, top: ui._normalY ?? ui.y, width: ui._normalWidth, height: ui._normalHeight } : { left: ui.x, top: ui.y, width: ui.width, height: ui.height };
return { maximized: ui.maximized, geometry };
},
setState: ({ maximized, geometry }) => {
const patch = {};
if (maximized !== void 0) patch.maximized = maximized;
if (geometry) {
if (maximized === true) {
patch._normalX = geometry.left;
patch._normalY = geometry.top;
patch._normalWidth = geometry.width;
patch._normalHeight = geometry.height;
} else {
patch.x = geometry.left;
patch.y = geometry.top;
patch.width = geometry.width;
patch.height = geometry.height;
}
}
Store2.updateUi(patch);
},
applyGeometry: () => updateWindowChrome(),
onStateChange: updateWindowButton,
onClose: () => setMinimized(true),
isActive: () => !Store2.getUi().minimized,
onViewportResize: () => {
centerPanel();
updateWindowChrome();
},
interactiveSelector: "button, input, select, textarea, a, .confluence-plus-header-actions"
});
render();
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() {
windowController?.destroy();
unsubscribe?.();
hideContextMenu();
unbindThemeListener();
removeAdoStyleEntry();
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({ 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.css
var styles_default = ` #confluence-plus-menu-overlay {\r
position: fixed;\r
inset: 0;\r
z-index: 99998;\r
background: rgba(9, 30, 66, .28);\r
backdrop-filter: blur(1px);\r
}\r
\r
.confluence-plus-floating-icon {\r
position: fixed;\r
right: 120px;\r
top: 0;\r
bottom: auto;\r
z-index: 100004;\r
display: inline-flex;\r
}\r
\r
.confluence-plus-floating-icon-image {\r
display: block;\r
width: 48px;\r
height: 48px;\r
cursor: pointer;\r
object-fit: contain;\r
}\r
\r
.confluence-plus-tooltip-host {\r
position: relative;\r
}\r
\r
.confluence-plus-floating-icon.confluence-plus-tooltip-host {\r
position: fixed;\r
}\r
\r
.confluence-plus-tooltip {\r
position: absolute;\r
top: calc(100% + 8px);\r
right: 0;\r
z-index: 100005;\r
padding: 8px 10px;\r
border-radius: 5px;\r
background: #172b4d;\r
box-shadow: 0 4px 12px rgba(9, 30, 66, .25);\r
color: #fff;\r
font-size: 12px;\r
line-height: 1.55;\r
opacity: 0;\r
pointer-events: none;\r
transform: translateY(-4px);\r
transition: opacity .15s ease, transform .15s ease;\r
visibility: hidden;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-tooltip-host:hover .confluence-plus-tooltip,\r
.confluence-plus-tooltip-host:focus-within .confluence-plus-tooltip {\r
opacity: 1;\r
transform: translateY(0);\r
visibility: visible;\r
}\r
\r
.confluence-plus-tooltip.confluence-plus-tooltip--right { bottom: auto; left: calc(100% + 10px); right: auto; top: 50%; transform: translateY(-50%) translateX(-4px); }\r
.confluence-plus-tooltip.confluence-plus-tooltip--bottom { left: auto; right: 0; top: calc(100% + 10px); transform: translateY(-4px); }\r
.confluence-plus-tooltip.confluence-plus-tooltip--left { left: auto; right: calc(100% + 10px); top: 50%; transform: translateY(-50%) translateX(4px); }\r
.confluence-plus-tooltip.confluence-plus-tooltip--top { bottom: calc(100% + 10px); right: 0; top: auto; transform: translateY(4px); }\r
.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); }\r
.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); }\r
\r
.confluence-plus-ado-modal {\r
align-items: center;\r
display: flex;\r
inset: 0;\r
justify-content: center;\r
position: fixed;\r
z-index: 100003;\r
}\r
\r
.confluence-plus-ado-modal[hidden] {\r
display: none;\r
}\r
\r
.confluence-plus-ado-modal-backdrop {\r
background: rgba(9, 30, 66, .32);\r
inset: 0;\r
position: absolute;\r
}\r
\r
.confluence-plus-ado-modal-panel {\r
align-self: center;\r
background: var(--confluence-plus-surface, #fff);\r
border-radius: 9px;\r
box-shadow: 0 18px 48px rgba(9, 30, 66, .22), 0 3px 12px rgba(9, 30, 66, .12);\r
color: var(--confluence-plus-text, #172b4d);\r
display: flex;\r
flex: 0 1 auto;\r
flex-direction: column;\r
height: min(695px, calc(100dvh - 32px));\r
max-height: calc(100dvh - 32px);\r
max-width: min(860px, calc(100vw - 36px));\r
overflow: hidden;\r
position: relative;\r
width: 860px;\r
}\r
\r
.confluence-plus-ado-modal-header {\r
align-items: center;\r
border-bottom: 1px solid var(--confluence-plus-border, #dfe1e6);\r
display: flex;\r
flex: 0 0 auto;\r
gap: 12px;\r
padding: 18px 20px 16px;\r
}\r
\r
.confluence-plus-ado-modal-mark {\r
border-radius: 8px;\r
display: block;\r
flex: none;\r
height: 36px;\r
object-fit: cover;\r
width: 36px;\r
}\r
\r
.confluence-plus-ado-modal-header > div {\r
min-width: 0;\r
}\r
\r
.confluence-plus-ado-modal-header strong,\r
.confluence-plus-ado-modal-header small {\r
display: block;\r
}\r
\r
.confluence-plus-ado-modal-header strong {\r
font-size: 16px;\r
line-height: 1.2;\r
}\r
\r
.confluence-plus-ado-modal-header small {\r
color: var(--confluence-plus-muted, #5e6c84);\r
font-size: 12px;\r
margin-top: 4px;\r
}\r
\r
.confluence-plus-ado-modal-close {\r
background: transparent;\r
border: 0;\r
border-radius: 5px;\r
color: var(--confluence-plus-muted, #6b778c);\r
cursor: pointer;\r
font-size: 22px;\r
height: 30px;\r
margin-left: auto;\r
width: 30px;\r
}\r
\r
.confluence-plus-ado-modal-close:hover {\r
background: var(--confluence-plus-hover, #ebecf0);\r
color: var(--confluence-plus-text, #172b4d);\r
}\r
\r
.confluence-plus-ado-modal-close:focus {\r
outline: 0;\r
}\r
\r
.confluence-plus-ado-modal-body {\r
flex: 1 1 0;\r
max-height: none;\r
min-height: 0;\r
overflow-y: auto;\r
padding: 14px;\r
scrollbar-color: var(--confluence-plus-border, #dfe1e6) transparent;\r
scrollbar-width: thin;\r
}\r
\r
.confluence-plus-ado-modal-body::-webkit-scrollbar {\r
width: 8px;\r
}\r
\r
.confluence-plus-ado-modal-body::-webkit-scrollbar-thumb {\r
background: var(--confluence-plus-border, #dfe1e6);\r
border-radius: 999px;\r
}\r
\r
.confluence-plus-modal-config-list {\r
display: grid;\r
gap: 8px;\r
min-height: 0;\r
}\r
\r
.confluence-plus-modal-config-section {\r
color: var(--confluence-plus-text, #172b4d);\r
font-size: 12px;\r
letter-spacing: .04em;\r
margin: 4px 2px 0;\r
text-transform: uppercase;\r
}\r
\r
.confluence-plus-modal-config-group {\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 9px;\r
overflow: hidden;\r
}\r
\r
.confluence-plus-modal-config-group[hidden] {\r
display: none !important;\r
}\r
\r
.confluence-plus-modal-config-summary {\r
align-items: center;\r
background: var(--confluence-plus-control, #f8fafc);\r
color: var(--confluence-plus-text, #172b4d);\r
cursor: pointer;\r
display: flex;\r
font-size: 12px;\r
font-weight: 700;\r
justify-content: flex-start;\r
letter-spacing: .04em;\r
list-style: none;\r
padding: 9px 36px 9px 12px;\r
position: relative;\r
text-transform: uppercase;\r
}\r
\r
.confluence-plus-modal-config-summary::-webkit-details-marker {\r
display: none;\r
}\r
\r
.confluence-plus-modal-config-summary:hover {\r
background: var(--confluence-plus-hover, #f1f5f9);\r
}\r
\r
.confluence-plus-modal-config-chevron {\r
align-items: center;\r
color: var(--confluence-plus-muted, #5e6c84);\r
display: inline-flex;\r
height: 18px;\r
justify-content: center;\r
position: absolute;\r
right: 12px;\r
top: 50%;\r
transform: translateY(-50%);\r
transition: transform .18s ease;\r
width: 18px;\r
}\r
\r
.confluence-plus-modal-config-chevron img {\r
display: block;\r
height: 16px;\r
width: 16px;\r
}\r
\r
.confluence-plus-modal-config-group[open] .confluence-plus-modal-config-chevron {\r
transform: translateY(-50%) rotate(180deg);\r
}\r
\r
.confluence-plus-modal-config-group-content {\r
display: grid;\r
gap: 8px;\r
padding: 8px;\r
}\r
\r
.confluence-plus-modal-config-row {\r
align-items: center;\r
background: var(--confluence-plus-control, #f4f5f7);\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 9px;\r
box-sizing: border-box;\r
display: grid;\r
gap: 14px;\r
grid-template-columns: minmax(0, 1fr) minmax(150px, 42%);\r
min-height: 60px;\r
padding: 9px 12px;\r
}\r
\r
.confluence-plus-modal-config-copy {\r
min-width: 0;\r
}\r
\r
.confluence-plus-modal-config-title,\r
.confluence-plus-modal-config-description {\r
display: block;\r
}\r
\r
.confluence-plus-modal-config-title {\r
color: var(--confluence-plus-text, #172b4d);\r
font-size: 13px;\r
font-weight: 650;\r
}\r
\r
.confluence-plus-modal-config-description {\r
color: var(--confluence-plus-muted, #5e6c84);\r
font-size: 11px;\r
line-height: 1.35;\r
margin-top: 4px;\r
}\r
\r
.confluence-plus-modal-config-control {\r
min-width: 0;\r
}\r
\r
.confluence-plus-modal-config-control input[type="number"],\r
.confluence-plus-modal-config-control select {\r
appearance: auto;\r
background: var(--confluence-plus-surface, #fff);\r
border: 2px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 7px;\r
box-sizing: border-box;\r
color: var(--confluence-plus-text, #172b4d);\r
font: inherit;\r
height: 40px;\r
outline: 0;\r
padding: 0 11px;\r
transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;\r
width: 100%;\r
}\r
\r
.confluence-plus-modal-config-control select {\r
cursor: pointer;\r
padding-right: 34px;\r
}\r
\r
.confluence-plus-modal-config-control input:focus,\r
.confluence-plus-modal-config-control select:focus {\r
border-color: var(--confluence-plus-accent, #0c66e4);\r
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 25%, transparent);\r
}\r
\r
.confluence-plus-modal-config-control input[type="number"]::-webkit-inner-spin-button,\r
.confluence-plus-modal-config-control input[type="number"]::-webkit-outer-spin-button {\r
-webkit-appearance: none;\r
margin: 0;\r
}\r
\r
.confluence-plus-modal-config-control input[type="number"] {\r
-moz-appearance: textfield;\r
}\r
\r
.confluence-plus-modal-number-control {\r
position: relative;\r
}\r
\r
.confluence-plus-modal-number-control input {\r
padding-right: 42px;\r
}\r
\r
.confluence-plus-modal-number-control em {\r
color: var(--confluence-plus-muted, #5e6c84);\r
font-size: 12px;\r
font-style: normal;\r
position: absolute;\r
right: 11px;\r
top: 10px;\r
}\r
\r
.confluence-plus-empty-settings {\r
color: var(--confluence-plus-muted, #5e6c84);\r
font-size: 12px;\r
padding: 14px 8px;\r
text-align: center;\r
}\r
\r
.confluence-plus-ai-config {\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 10px;\r
overflow: hidden;\r
}\r
\r
.confluence-plus-ai-config-summary {\r
align-items: center;\r
background: var(--confluence-plus-control, #f8fafc);\r
box-sizing: border-box;\r
cursor: pointer;\r
display: flex;\r
gap: 14px;\r
justify-content: space-between;\r
list-style: none;\r
min-height: 60px;\r
padding: 9px 12px;\r
}\r
\r
.confluence-plus-ai-config-summary::-webkit-details-marker {\r
display: none;\r
}\r
\r
.confluence-plus-ai-config-summary:hover {\r
background: var(--confluence-plus-hover, #f1f5f9);\r
}\r
\r
.confluence-plus-ai-config-copy {\r
min-width: 0;\r
}\r
\r
.confluence-plus-ai-config-summary-right {\r
align-items: center;\r
display: flex;\r
flex: none;\r
gap: 10px;\r
}\r
\r
.confluence-plus-ai-status {\r
align-items: center;\r
border-radius: 999px;\r
display: inline-flex;\r
font-size: 11px;\r
font-weight: 650;\r
gap: 6px;\r
padding: 6px 9px;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-ai-status::before {\r
border-radius: 50%;\r
content: "";\r
height: 7px;\r
width: 7px;\r
}\r
\r
.confluence-plus-ai-status.ai-status-unconfigured {\r
background: #fff0b3;\r
color: #974f0c;\r
}\r
\r
.confluence-plus-ai-status.ai-status-unconfigured::before {\r
background: #ff991f;\r
}\r
\r
.confluence-plus-ai-status.ai-status-available {\r
background: #e3fcef;\r
color: #00875a;\r
}\r
\r
.confluence-plus-ai-status.ai-status-available::before {\r
background: #36b37e;\r
}\r
\r
.confluence-plus-ai-chevron {\r
align-items: center;\r
display: inline-flex;\r
height: 18px;\r
justify-content: center;\r
line-height: 1;\r
transform: translateY(0);\r
transition: transform .18s ease;\r
width: 18px;\r
}\r
\r
.confluence-plus-ai-chevron img {\r
display: block;\r
height: 16px;\r
width: 16px;\r
}\r
\r
.confluence-plus-ai-config[open] .confluence-plus-ai-chevron {\r
transform: rotate(180deg);\r
}\r
\r
.confluence-plus-ai-config-content {\r
border-top: 1px solid var(--confluence-plus-border, #dfe1e6);\r
max-width: 820px;\r
padding: 18px 14px 14px;\r
}\r
\r
.confluence-plus-ai-field {\r
margin-bottom: 14px;\r
}\r
\r
.confluence-plus-ai-field label {\r
color: var(--confluence-plus-text, #172b4d);\r
display: block;\r
font-size: 12px;\r
font-weight: 650;\r
margin-bottom: 6px;\r
}\r
\r
.confluence-plus-ai-field small {\r
color: var(--confluence-plus-muted, #5e6c84);\r
display: block;\r
font-size: 11px;\r
line-height: 1.35;\r
margin: -2px 0 7px;\r
}\r
\r
.confluence-plus-ai-field input,\r
.confluence-plus-ai-field select {\r
background: var(--confluence-plus-surface, #fff);\r
border: 2px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 7px;\r
box-sizing: border-box;\r
color: var(--confluence-plus-text, #172b4d);\r
font: inherit;\r
font-size: 13px;\r
height: 40px;\r
outline: 0;\r
padding: 0 11px;\r
width: 100%;\r
}\r
\r
.confluence-plus-ai-field input:focus,\r
.confluence-plus-ai-field select:focus {\r
border-color: var(--confluence-plus-accent, #0c66e4);\r
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 25%, transparent);\r
}\r
\r
.confluence-plus-ai-password-wrap {\r
position: relative;\r
}\r
\r
.confluence-plus-ai-password-wrap input {\r
padding-right: 70px;\r
}\r
\r
.confluence-plus-ai-show-key {\r
background: transparent;\r
border: 0;\r
color: var(--confluence-plus-accent, #0c66e4);\r
cursor: pointer;\r
font-size: 11px;\r
font-weight: 650;\r
position: absolute;\r
right: 10px;\r
top: 11px;\r
}\r
\r
.confluence-plus-ai-actions {\r
align-items: center;\r
display: flex;\r
flex-wrap: nowrap;\r
gap: 10px;\r
justify-content: space-between;\r
max-width: 100%;\r
}\r
\r
.confluence-plus-ai-action-buttons {\r
align-items: center;\r
display: flex;\r
flex: 0 0 auto;\r
gap: 10px;\r
margin-left: auto;\r
}\r
\r
.confluence-plus-ai-actions button {\r
border: 0;\r
border-radius: 7px;\r
cursor: pointer;\r
flex: 0 0 auto;\r
font: inherit;\r
font-size: 12px;\r
font-weight: 650;\r
min-height: 38px;\r
padding: 0 14px;\r
white-space: nowrap;\r
width: auto;\r
}\r
\r
.confluence-plus-ai-primary {\r
background: var(--confluence-plus-accent, #0c66e4);\r
color: #fff;\r
}\r
\r
.confluence-plus-ai-primary:hover {\r
filter: brightness(.92);\r
}\r
\r
.confluence-plus-ai-secondary {\r
background: var(--confluence-plus-hover, #ebecf0);\r
color: var(--confluence-plus-text, #172b4d);\r
}\r
\r
.confluence-plus-ai-actions button:disabled {\r
cursor: wait;\r
opacity: .65;\r
}\r
\r
.confluence-plus-ai-result {\r
color: var(--confluence-plus-muted, #5e6c84);\r
flex: 1 1 auto;\r
font-size: 11px;\r
line-height: 1.4;\r
margin: 0;\r
min-width: 0;\r
overflow-wrap: anywhere;\r
}\r
\r
.confluence-plus-ai-action-buttons .confluence-plus-ai-primary {\r
min-width: 112px;\r
}\r
\r
.confluence-plus-ai-tooltip-host {\r
display: inline-flex;\r
position: relative;\r
}\r
\r
.confluence-plus-ai-tooltip {\r
background: var(--confluence-plus-text, #172b4d);\r
border: 1px solid color-mix(in srgb, var(--confluence-plus-surface, #fff) 18%, transparent);\r
border-radius: 8px;\r
bottom: calc(100% + 10px);\r
box-shadow: 0 8px 22px rgba(0, 0, 0, .28);\r
color: var(--confluence-plus-surface, #fff);\r
font-size: 12px;\r
font-weight: 500;\r
left: 50%;\r
line-height: 1.45;\r
max-width: 280px;\r
opacity: 0;\r
padding: 9px 12px;\r
pointer-events: none;\r
position: absolute;\r
text-align: left;\r
transform: translate(-50%, 4px);\r
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;\r
visibility: hidden;\r
white-space: normal;\r
width: max-content;\r
z-index: 10004;\r
}\r
\r
.confluence-plus-ai-tooltip::after {\r
border: 5px solid transparent;\r
border-top-color: var(--confluence-plus-text, #172b4d);\r
content: "";\r
left: 50%;\r
position: absolute;\r
top: 100%;\r
transform: translateX(-50%);\r
}\r
\r
.confluence-plus-ai-tooltip-host:hover .confluence-plus-ai-tooltip,\r
.confluence-plus-ai-tooltip-host:focus-within .confluence-plus-ai-tooltip {\r
opacity: 1;\r
transform: translate(-50%, 0);\r
visibility: visible;\r
}\r
\r
.confluence-plus-ai-result.ai-result-success {\r
color: #00875a;\r
}\r
\r
.confluence-plus-ai-result.ai-result-error {\r
color: #de350b;\r
}\r
\r
.confluence-plus-theme-dark .confluence-plus-ai-chevron img {\r
filter: brightness(0) invert(1);\r
}\r
\r
@media (max-width: 700px) {\r
.confluence-plus-ai-actions {\r
align-items: stretch;\r
flex-wrap: wrap;\r
}\r
\r
.confluence-plus-ai-action-buttons {\r
margin-left: auto;\r
}\r
\r
.confluence-plus-ai-result {\r
flex-basis: 100%;\r
margin-top: 2px;\r
}\r
}\r
\r
.confluence-plus-theme {\r
--confluence-plus-bg: #fff;\r
--confluence-plus-surface: #fff;\r
--confluence-plus-text: #172b4d;\r
--confluence-plus-muted: #5e6c84;\r
--confluence-plus-border: #dfe1e6;\r
--confluence-plus-control: #f4f5f7;\r
--confluence-plus-accent: #0c66e4;\r
--confluence-plus-hover: #ebecf0;\r
--confluence-plus-backdrop: rgba(9, 30, 66, .32);\r
color-scheme: light;\r
}\r
\r
.confluence-plus-theme-dark {\r
--confluence-plus-bg: #101214;\r
--confluence-plus-surface: #1d2125;\r
--confluence-plus-text: #e6edf3;\r
--confluence-plus-muted: #a8b3c2;\r
--confluence-plus-border: #3b434d;\r
--confluence-plus-control: #252b33;\r
--confluence-plus-accent: #579dff;\r
--confluence-plus-hover: #30363d;\r
--confluence-plus-backdrop: rgba(0, 0, 0, .58);\r
color-scheme: dark;\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-backdrop {\r
background: var(--confluence-plus-backdrop);\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-panel {\r
background: var(--confluence-plus-surface);\r
color: var(--confluence-plus-text);\r
box-shadow: 0 18px 48px var(--confluence-plus-backdrop);\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-header {\r
border-color: var(--confluence-plus-border);\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-header small,\r
.confluence-plus-theme .confluence-plus-modal-config-description,\r
.confluence-plus-theme .confluence-plus-modal-number-control em {\r
color: var(--confluence-plus-muted);\r
}\r
\r
.confluence-plus-theme .confluence-plus-modal-config-row {\r
background: var(--confluence-plus-control);\r
border-color: var(--confluence-plus-border);\r
}\r
\r
.confluence-plus-theme .confluence-plus-modal-config-title,\r
.confluence-plus-theme .confluence-plus-modal-config-summary,\r
.confluence-plus-theme .confluence-plus-modal-config-section {\r
color: var(--confluence-plus-text);\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-close {\r
color: var(--confluence-plus-muted);\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-close:hover,\r
.confluence-plus-theme .confluence-plus-modal-config-control input,\r
.confluence-plus-theme .confluence-plus-modal-config-control select {\r
background: var(--confluence-plus-surface);\r
border-color: var(--confluence-plus-border);\r
color: var(--confluence-plus-text);\r
}\r
\r
.confluence-plus-theme .confluence-plus-ado-modal-close:hover {\r
background: var(--confluence-plus-hover);\r
}\r
\r
.confluence-plus-theme .confluence-plus-modal-config-control input:focus,\r
.confluence-plus-theme .confluence-plus-modal-config-control select:focus {\r
border-color: var(--confluence-plus-accent);\r
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent) 25%, transparent);\r
}\r
\r
.confluence-plus-theme .confluence-plus-modal-config-chevron img {\r
filter: none;\r
}\r
\r
.confluence-plus-theme-dark .confluence-plus-modal-config-chevron img {\r
filter: brightness(0) invert(1);\r
}\r
\r
.confluence-plus-theme .confluence-plus-empty-settings {\r
color: var(--confluence-plus-muted);\r
}\r
\r
@media (max-width: 620px) {\r
.confluence-plus-ado-modal-panel {\r
height: calc(100dvh - 16px);\r
max-width: calc(100vw - 16px);\r
}\r
\r
.confluence-plus-modal-config-row {\r
align-items: stretch;\r
flex-direction: column;\r
}\r
\r
.confluence-plus-modal-config-control {\r
width: 100%;\r
}\r
\r
.confluence-plus-modal-config-control select,\r
.confluence-plus-modal-number-control input {\r
width: 100%;\r
}\r
}\r
\r
#confluence-plus-root {\r
position: fixed;\r
z-index: 100000;\r
font-size: 14px;\r
color: var(--confluence-plus-text, #333);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-inline-feature-content,\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-modal-status,\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-title {\r
color: var(--confluence-plus-text);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-modal-hint {\r
color: var(--confluence-plus-muted);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-label-manual-input,\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-label-edit {\r
background: var(--confluence-plus-surface);\r
border-color: var(--confluence-plus-border);\r
color: var(--confluence-plus-text);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-secondary-btn {\r
background: var(--confluence-plus-surface);\r
border-color: var(--confluence-plus-accent);\r
color: var(--confluence-plus-accent);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-popconfirm {\r
background: var(--confluence-plus-surface);\r
border-color: var(--confluence-plus-border);\r
color: var(--confluence-plus-text);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-popconfirm-message {\r
color: var(--confluence-plus-muted);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-popconfirm::after {\r
background: var(--confluence-plus-surface);\r
border-color: var(--confluence-plus-border);\r
}\r
\r
#confluence-plus-root.confluence-plus-theme .confluence-plus-confirm-cancel {\r
background: var(--confluence-plus-hover);\r
color: var(--confluence-plus-text);\r
}\r
\r
.confluence-plus-context-menu {\r
position: fixed;\r
/* Keep the menu above the expanded root (z-index: 999999), matching\r
the layering used by the pre-refactor UI. */\r
z-index: 1000003;\r
width: 220px;\r
box-sizing: border-box;\r
border: 1px solid #dfe1e6;\r
border-radius: 6px;\r
background: white;\r
box-shadow: 0 8px 24px rgba(9, 30, 66, .24);\r
padding: 4px 0;\r
color: #172b4d;\r
font-size: 12px;\r
}\r
\r
.confluence-plus-context-menu-item,\r
.confluence-plus-context-menu-action {\r
display: block;\r
width: 100%;\r
box-sizing: border-box;\r
border: none;\r
background: transparent;\r
padding: 9px 12px;\r
text-align: left;\r
white-space: nowrap;\r
user-select: none;\r
}\r
\r
.confluence-plus-context-menu-action {\r
color: #172b4d;\r
cursor: pointer;\r
font: inherit;\r
}\r
\r
.confluence-plus-context-menu-action:hover {\r
background: #deebff;\r
color: #0747a6;\r
}\r
\r
.confluence-plus-context-menu-info {\r
color: #8993a4;\r
cursor: default;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window.confluence-plus-expanded {\r
background: var(--confluence-plus-surface, #fff);\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 12px;\r
box-shadow: 0 18px 48px rgba(9, 30, 66, .22);\r
color: var(--confluence-plus-text, #172b4d);\r
overflow: hidden;\r
box-sizing: border-box;\r
resize: both;\r
min-width: min(720px, calc(100vw - 20px));\r
min-height: min(520px, calc(100vh - 20px));\r
max-width: 100vw;\r
max-height: 100vh;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-header {\r
align-items: center;\r
background: #172b4d;\r
color: #fff;\r
display: flex;\r
flex: none;\r
gap: 12px;\r
height: 62px;\r
min-height: 62px;\r
padding: 0 16px;\r
user-select: none;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-mark {\r
align-items: center;\r
background: #0c66e4;\r
border-radius: 8px;\r
display: block;\r
flex: none;\r
height: 34px;\r
object-fit: cover;\r
width: 38px;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy {\r
min-width: 0;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy strong,\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy small {\r
display: block;\r
overflow: hidden;\r
text-overflow: ellipsis;\r
white-space: nowrap;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy strong {\r
font-size: 16px;\r
line-height: 1.2;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-title-copy small {\r
color: #c7d3e3;\r
font-size: 11px;\r
margin-top: 2px;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-spacer {\r
flex: 1;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-header-actions {\r
flex: none;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-action {\r
background: transparent;\r
border: 0;\r
border-radius: 6px;\r
color: #dbe7f5;\r
cursor: pointer;\r
display: inline-flex;\r
align-items: center;\r
justify-content: center;\r
line-height: 1;\r
padding: 0;\r
font-size: 18px;\r
height: 32px;\r
width: 34px;\r
}\r
\r
.confluence-plus-header-actions button svg {\r
width: 18px;\r
height: 18px;\r
display: block;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-action:hover {\r
background: rgba(255, 255, 255, .14);\r
color: #fff;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-close {\r
font-size: 24px;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-body {\r
display: flex;\r
flex: 1;\r
height: calc(100% - 62px);\r
min-height: 0;\r
position: relative;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-scroll {\r
display: flex;\r
flex: 1;\r
flex-direction: column;\r
gap: 14px;\r
min-height: 0;\r
overflow: auto;\r
padding: 16px 18px 22px;\r
scrollbar-color: var(--confluence-plus-border, #dfe1e6) transparent;\r
scrollbar-width: thin;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-scroll::-webkit-scrollbar {\r
width: 8px;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-window-scroll::-webkit-scrollbar-thumb {\r
background: var(--confluence-plus-border, #dfe1e6);\r
border-radius: 999px;\r
}\r
\r
.confluence-plus-content-feature-list {\r
display: grid;\r
gap: 12px;\r
}\r
\r
.confluence-plus-content-feature {\r
background: var(--confluence-plus-surface, #fff);\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 10px;\r
overflow: hidden;\r
}\r
\r
.confluence-plus-content-feature-summary {\r
align-items: center;\r
background: var(--confluence-plus-control, #f8fafc);\r
box-sizing: border-box;\r
cursor: pointer;\r
display: flex;\r
gap: 14px;\r
height: 60px;\r
justify-content: space-between;\r
list-style: none;\r
min-height: 60px;\r
padding: 9px 12px;\r
}\r
\r
.confluence-plus-content-feature-summary::-webkit-details-marker {\r
display: none;\r
}\r
\r
.confluence-plus-content-feature-summary:hover {\r
background: var(--confluence-plus-hover, #f1f5f9);\r
}\r
\r
.confluence-plus-content-feature-copy {\r
flex: 1;\r
min-width: 0;\r
}\r
\r
.confluence-plus-content-feature-copy strong,\r
.confluence-plus-content-feature-copy small {\r
display: block;\r
}\r
\r
.confluence-plus-content-feature-copy strong {\r
color: var(--confluence-plus-text, #172b4d);\r
font-size: 13px;\r
font-weight: 650;\r
}\r
\r
.confluence-plus-content-feature-copy small {\r
color: var(--confluence-plus-muted, #5e6c84);\r
font-size: 11px;\r
line-height: 1.35;\r
margin-top: 4px;\r
overflow: hidden;\r
text-overflow: ellipsis;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-content-feature-ai-label {\r
color: var(--confluence-plus-accent, #0052cc);\r
font-weight: 700;\r
margin-right: 4px;\r
}\r
\r
.confluence-plus-content-feature-chevron {\r
align-items: center;\r
display: inline-flex;\r
flex: none;\r
height: 20px;\r
justify-content: center;\r
transition: transform .18s ease;\r
width: 20px;\r
}\r
\r
.confluence-plus-content-feature-chevron img {\r
display: block;\r
height: 18px;\r
width: 18px;\r
}\r
\r
.confluence-plus-content-feature[open] .confluence-plus-content-feature-chevron {\r
transform: rotate(180deg);\r
}\r
\r
.confluence-plus-content-feature-body {\r
border-top: 1px solid var(--confluence-plus-border, #dfe1e6);\r
padding: 0;\r
}\r
\r
.confluence-plus-feature-inline-host {\r
min-width: 0;\r
}\r
\r
.confluence-plus-inline-feature-content {\r
box-sizing: border-box;\r
color: var(--confluence-plus-text, #172b4d);\r
min-width: 0;\r
padding: 12px;\r
}\r
\r
.confluence-plus-feature-inline-host .confluence-plus-space-tree {\r
max-height: 360px;\r
}\r
\r
.confluence-plus-inline-feature-error {\r
color: #de350b;\r
font-size: 12px;\r
line-height: 1.45;\r
padding: 12px;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-feature-summary:focus-visible {\r
outline: 2px solid var(--confluence-plus-accent, #0c66e4);\r
outline-offset: 2px;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window .confluence-plus-content-feature-chevron img {\r
filter: none;\r
}\r
\r
#confluence-plus-root.confluence-plus-content-window.confluence-plus-theme-dark .confluence-plus-content-feature-chevron img {\r
filter: brightness(0) invert(1);\r
}\r
\r
@media (max-width: 720px) {\r
#confluence-plus-root.confluence-plus-content-window.confluence-plus-expanded {\r
min-width: 0;\r
width: calc(100vw - 16px) !important;\r
}\r
\r
}\r
\r
#confluence-plus-root.confluence-plus-expanded {\r
background: white;\r
border: 1px solid #ddd;\r
border-radius: 12px;\r
box-shadow: 0 8px 24px rgba(0, 0, 0, .15);\r
overflow: hidden;\r
box-sizing: border-box;\r
resize: both;\r
min-width: var(--confluence-plus-ui-min-width);\r
min-height: var(--confluence-plus-ui-min-height);\r
max-width: 100vw;\r
max-height: 100vh;\r
}\r
\r
.confluence-plus-header {\r
height: 48px;\r
padding: 0 16px;\r
background: #0052cc;\r
color: white;\r
display: flex;\r
align-items: center;\r
justify-content: space-between;\r
cursor: move;\r
user-select: none;\r
}\r
\r
.confluence-plus-header-title {\r
flex: 0 0 auto;\r
min-width: 0;\r
}\r
\r
.confluence-plus-session-label {\r
margin-top: 2px;\r
font-size: 11px;\r
opacity: .8;\r
}\r
\r
.confluence-plus-header-actions {\r
display: flex;\r
align-items: center;\r
gap: 6px;\r
}\r
\r
.confluence-plus-header button {\r
width: 28px;\r
height: 28px;\r
border: none;\r
border-radius: 6px;\r
background: rgba(255, 255, 255, .2);\r
color: white;\r
font-size: 18px;\r
line-height: 1;\r
cursor: pointer;\r
}\r
\r
.confluence-plus-header-icon-btn {\r
font-size: 15px !important;\r
}\r
\r
.confluence-plus-section {\r
margin-bottom: 20px;\r
}\r
\r
.confluence-plus-title {\r
margin-bottom: 12px;\r
font-weight: bold;\r
}\r
\r
.confluence-plus-tree-toolbar {\r
display: flex;\r
align-items: center;\r
justify-content: space-between;\r
gap: 8px;\r
margin-bottom: 10px;\r
}\r
\r
.confluence-plus-tree-refresh {\r
width: 28px;\r
height: 28px;\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 6px;\r
background: var(--confluence-plus-surface, #fff);\r
color: var(--confluence-plus-text, #172b4d);\r
cursor: pointer;\r
font-size: 17px;\r
}\r
\r
.confluence-plus-tree-refresh:hover {\r
background: var(--confluence-plus-hover, #ebecf0);\r
}\r
\r
.confluence-plus-space-tree {\r
max-height: calc(100% - 48px);\r
overflow: auto;\r
padding: 4px 0;\r
}\r
\r
.confluence-plus-tree-list {\r
margin: 0;\r
padding-left: 0;\r
list-style: none;\r
}\r
\r
.confluence-plus-tree-item .confluence-plus-tree-list {\r
margin-left: 18px;\r
}\r
\r
.confluence-plus-tree-row {\r
position: relative;\r
display: flex;\r
align-items: center;\r
gap: 6px;\r
min-height: 32px;\r
border-radius: 6px;\r
padding: 3px 6px;\r
}\r
\r
.confluence-plus-tree-row:hover {\r
background: var(--confluence-plus-hover, #f4f5f7);\r
}\r
\r
.confluence-plus-tree-current {\r
background: color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 16%, var(--confluence-plus-surface, #fff));\r
color: var(--confluence-plus-accent, #0747a6);\r
font-weight: 600;\r
}\r
\r
.confluence-plus-tree-toggle,\r
.confluence-plus-tree-toggle-spacer {\r
width: 22px;\r
height: 24px;\r
flex: 0 0 22px;\r
}\r
\r
.confluence-plus-tree-toggle {\r
border: none;\r
border-radius: 4px;\r
background: transparent;\r
color: var(--confluence-plus-muted, #42526e);\r
cursor: pointer;\r
font-size: 15px;\r
line-height: 1;\r
}\r
\r
.confluence-plus-tree-toggle:hover {\r
background: var(--confluence-plus-hover, #dfe1e6);\r
}\r
\r
.confluence-plus-tree-title-wrap {\r
min-width: 0;\r
flex: 1;\r
display: flex;\r
align-items: baseline;\r
overflow: hidden;\r
}\r
\r
.confluence-plus-tree-title {\r
min-width: 0;\r
flex: 0 1 auto;\r
overflow: hidden;\r
text-overflow: ellipsis;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-tree-child-count {\r
flex: 0 0 auto;\r
margin-left: 4px;\r
color: var(--confluence-plus-accent, #0052cc);\r
font-size: 11px;\r
font-weight: 600;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-tree-current-badge {\r
flex: 0 0 auto;\r
margin-left: 4px;\r
border-radius: 999px;\r
background: var(--confluence-plus-accent, #0052cc);\r
color: var(--confluence-plus-surface, #fff);\r
padding: 2px 6px;\r
font-size: 10px;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-label-toolbar {\r
align-items: end;\r
background: var(--confluence-plus-control, #f7f9fb);\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 9px;\r
display: grid;\r
gap: 14px;\r
grid-template-columns: minmax(0, 1fr) auto;\r
padding: 10px 12px;\r
}\r
\r
.confluence-plus-label-toolbar-copy {\r
min-width: 0;\r
}\r
\r
.confluence-plus-label-toolbar-copy .confluence-plus-title {\r
color: var(--confluence-plus-text, #172b4d);\r
margin: 0;\r
}\r
\r
.confluence-plus-label-toolbar-copy .confluence-plus-modal-hint {\r
margin-top: 4px;\r
}\r
\r
.confluence-plus-label-generate-button,\r
.confluence-plus-label-apply-button {\r
align-self: end;\r
background: var(--confluence-plus-accent, #0c66e4);\r
border: 0;\r
border-radius: 7px;\r
color: #fff;\r
cursor: pointer;\r
font: inherit;\r
font-size: 12px;\r
font-weight: 650;\r
height: 40px;\r
padding: 0 14px;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-label-generate-button {\r
min-width: 190px;\r
}\r
\r
.confluence-plus-label-generate-button:hover:not(:disabled),\r
.confluence-plus-label-apply-button:hover:not(:disabled) {\r
filter: brightness(.92);\r
}\r
\r
.confluence-plus-label-generate-button:disabled,\r
.confluence-plus-label-apply-button:disabled {\r
cursor: wait;\r
opacity: .6;\r
}\r
\r
.confluence-plus-label-results-header {\r
color: var(--confluence-plus-text, #172b4d);\r
font-size: 13px;\r
font-weight: 700;\r
margin: 14px 0 8px;\r
max-width: 100%;\r
}\r
\r
.confluence-plus-label-results-header-text {\r
display: block;\r
overflow: hidden;\r
text-overflow: ellipsis;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-label-title-tooltip {\r
position: relative;\r
}\r
\r
.confluence-plus-label-title-tooltip::after {\r
background: var(--confluence-plus-text, #172b4d);\r
border: 1px solid color-mix(in srgb, var(--confluence-plus-surface, #fff) 18%, transparent);\r
border-radius: 7px;\r
bottom: calc(100% + 8px);\r
box-shadow: 0 8px 22px rgba(0, 0, 0, .28);\r
color: var(--confluence-plus-surface, #fff);\r
content: attr(data-confluence-plus-tooltip);\r
font-size: 11px;\r
font-weight: 500;\r
left: 0;\r
line-height: 1.4;\r
max-width: min(420px, calc(100vw - 40px));\r
opacity: 0;\r
padding: 8px 10px;\r
pointer-events: none;\r
position: absolute;\r
transform: translateY(4px);\r
transition: opacity .15s ease, transform .15s ease, visibility .15s ease;\r
visibility: hidden;\r
white-space: normal;\r
width: max-content;\r
z-index: 10004;\r
}\r
\r
.confluence-plus-label-title-tooltip:hover::after,\r
.confluence-plus-label-title-tooltip:focus-visible::after {\r
opacity: 1;\r
transform: translateY(0);\r
visibility: visible;\r
}\r
\r
.confluence-plus-label-list {\r
display: grid;\r
gap: 10px;\r
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));\r
margin-bottom: 12px;\r
}\r
\r
.confluence-plus-label-item {\r
align-items: center;\r
background: var(--confluence-plus-control, #f7f9fb);\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 8px;\r
display: grid;\r
gap: 8px;\r
grid-template-columns: 18px minmax(0, 1fr) 28px;\r
min-height: 42px;\r
padding: 7px 8px;\r
}\r
\r
.confluence-plus-label-item input[type="checkbox"] {\r
accent-color: var(--confluence-plus-accent, #0c66e4);\r
height: 16px;\r
width: 16px;\r
}\r
\r
.confluence-plus-label-edit,\r
.confluence-plus-label-manual-input {\r
background: var(--confluence-plus-surface, #fff);\r
border: 1px solid var(--confluence-plus-border, #dfe1e6);\r
border-radius: 6px;\r
box-sizing: border-box;\r
color: var(--confluence-plus-text, #172b4d);\r
font: inherit;\r
height: 32px;\r
min-width: 0;\r
padding: 0 9px;\r
width: 100%;\r
}\r
\r
.confluence-plus-label-edit:focus,\r
.confluence-plus-label-manual-input:focus {\r
border-color: var(--confluence-plus-accent, #0c66e4);\r
box-shadow: 0 0 0 2px color-mix(in srgb, var(--confluence-plus-accent, #0c66e4) 25%, transparent);\r
outline: 0;\r
}\r
\r
.confluence-plus-label-remove {\r
width: 28px;\r
height: 28px;\r
border: none;\r
border-radius: 6px;\r
background: #ffebe6;\r
color: #bf2600;\r
cursor: pointer;\r
font-size: 16px;\r
}\r
\r
.confluence-plus-label-add-row {\r
display: grid;\r
gap: 8px;\r
grid-template-columns: minmax(0, 1fr) auto;\r
margin-bottom: 12px;\r
}\r
\r
.confluence-plus-label-apply-row {\r
display: flex;\r
justify-content: flex-end;\r
margin-top: 12px;\r
}\r
\r
.confluence-plus-label-apply-tooltip-host {\r
display: inline-flex;\r
position: relative;\r
}\r
\r
.confluence-plus-label-apply-tooltip-host::after {\r
background: var(--confluence-plus-text, #172b4d);\r
border: 1px solid color-mix(in srgb, var(--confluence-plus-surface, #fff) 18%, transparent);\r
border-radius: 7px;\r
bottom: calc(100% + 9px);\r
color: #fff;\r
content: attr(data-tooltip);\r
font-size: 11px;\r
line-height: 1.35;\r
max-width: 260px;\r
opacity: 0;\r
padding: 7px 9px;\r
pointer-events: none;\r
position: absolute;\r
right: 0;\r
transform: translateY(4px);\r
transition: opacity .15s ease, transform .15s ease;\r
visibility: hidden;\r
white-space: normal;\r
width: max-content;\r
z-index: 100006;\r
}\r
\r
.confluence-plus-label-apply-tooltip-host:hover::after,\r
.confluence-plus-label-apply-tooltip-host:focus-within::after,\r
.confluence-plus-label-apply-tooltip-host:focus::after {\r
opacity: 1;\r
transform: translateY(0);\r
visibility: visible;\r
}\r
\r
#confluence-plus-root.confluence-plus-theme-dark .confluence-plus-label-apply-tooltip-host::after {\r
background: #172b4d;\r
border-color: #4c9aff;\r
color: #fff;\r
}\r
\r
@media (max-width: 700px) {\r
.confluence-plus-label-toolbar {\r
grid-template-columns: 1fr;\r
}\r
\r
.confluence-plus-label-generate-button {\r
width: 100%;\r
}\r
\r
.confluence-plus-label-apply-row {\r
justify-content: stretch;\r
}\r
\r
.confluence-plus-label-apply-button {\r
width: 100%;\r
}\r
}\r
\r
.confluence-plus-popconfirm-wrap {\r
position: fixed;\r
z-index: 2147483001;\r
width: 300px;\r
}\r
\r
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm {\r
background: #172b4d;\r
border-color: #4c9aff;\r
color: #fff;\r
}\r
\r
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm::after {\r
background: #172b4d;\r
border-color: #4c9aff;\r
}\r
\r
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm-title {\r
color: #fff;\r
}\r
\r
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-popconfirm-message {\r
color: #c7d3e3;\r
}\r
\r
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-confirm-cancel {\r
background: #253858;\r
color: #fff;\r
}\r
\r
.confluence-plus-popconfirm-wrap.is-dark .confluence-plus-confirm-ok {\r
background: #4c9aff;\r
color: #172b4d;\r
}\r
\r
.confluence-plus-popconfirm {\r
position: relative;\r
border: 1px solid #dfe1e6;\r
border-radius: 8px;\r
background: white;\r
box-shadow: 0 8px 24px rgba(9, 30, 66, .22);\r
padding: 14px;\r
}\r
\r
.confluence-plus-popconfirm::after {\r
content: '';\r
position: absolute;\r
left: 24px;\r
bottom: -7px;\r
width: 12px;\r
height: 12px;\r
border-right: 1px solid #dfe1e6;\r
border-bottom: 1px solid #dfe1e6;\r
background: white;\r
transform: rotate(45deg);\r
}\r
\r
.confluence-plus-popconfirm-title {\r
color: #172b4d;\r
font-size: 14px;\r
font-weight: 700;\r
}\r
\r
.confluence-plus-popconfirm-message {\r
margin-top: 7px;\r
color: #42526e;\r
font-size: 12px;\r
line-height: 1.45;\r
word-break: break-word;\r
}\r
\r
.confluence-plus-popconfirm-actions {\r
display: flex;\r
justify-content: flex-end;\r
gap: 8px;\r
margin-top: 12px;\r
}\r
\r
.confluence-plus-popconfirm-actions button {\r
border: none;\r
border-radius: 6px;\r
padding: 6px 10px;\r
cursor: pointer;\r
font-size: 12px;\r
}\r
\r
.confluence-plus-confirm-cancel {\r
background: #f1f2f4;\r
color: #172b4d;\r
}\r
\r
.confluence-plus-confirm-ok {\r
background: #0052cc;\r
color: white;\r
}\r
\r
.confluence-plus-label-add-row .confluence-plus-secondary-btn {\r
width: auto;\r
}\r
\r
.confluence-plus-feature-btn {\r
margin-bottom: 10px;\r
}\r
\r
.confluence-plus-secondary-btn {\r
width: 100%;\r
border: 1px solid #0052cc;\r
border-radius: 8px;\r
background: white;\r
color: #0052cc;\r
padding: 9px;\r
cursor: pointer;\r
display: flex;\r
align-items: center;\r
justify-content: space-between;\r
gap: 8px;\r
text-align: left;\r
}\r
\r
.confluence-plus-status-badge {\r
display: inline-block;\r
border-radius: 999px;\r
padding: 3px 7px;\r
font-size: 11px;\r
font-weight: 600;\r
white-space: nowrap;\r
}\r
\r
.confluence-plus-status-badge.not-configured {\r
background: #f1f2f4;\r
color: #6b778c;\r
}\r
\r
.confluence-plus-status-badge.unverified {\r
background: #fff0b3;\r
color: #7f5f01;\r
}\r
\r
.confluence-plus-status-badge.available {\r
background: #abf5d1;\r
color: #006644;\r
}\r
\r
.confluence-plus-status-badge.unavailable {\r
background: #ffbdad;\r
color: #bf2600;\r
}\r
\r
.confluence-plus-modal-hint {\r
margin-top: 6px;\r
color: var(--confluence-plus-muted, #6b778c);\r
font-size: 11px;\r
line-height: 1.4;\r
}\r
\r
.confluence-plus-modal-status {\r
min-height: 18px;\r
margin-top: 10px;\r
font-size: 12px;\r
white-space: pre-wrap;\r
}\r
\r
.confluence-plus-modal-status.success {\r
color: #16803c;\r
}\r
\r
.confluence-plus-modal-status.error {\r
color: #d93025;\r
}\r
\r
.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; }\r
.confluence-plus-toast-stack[hidden] { display: none; }\r
.confluence-plus-toast-stack .confluence-plus-toast { bottom: auto; max-width: none; opacity: 0; pointer-events: auto; position: relative; right: auto; top: auto; transform: translateX(18px); transition: opacity .2s ease, transform .2s ease; visibility: hidden; width: 100%; }\r
.confluence-plus-toast-stack .confluence-plus-toast[hidden] { display: none; }\r
.confluence-plus-toast-stack .confluence-plus-toast.is-visible { opacity: 1; transform: translateX(0); visibility: visible; }\r
.confluence-plus-toast-stack .confluence-plus-toast.is-leaving { opacity: 0; transform: translateX(18px); }\r
`;
// src/shared/toast.css
var toast_default = "\r\n[dft-data-toast-stack] {\r\n --toast-success-bg: #eaf7ef;\r\n --toast-success-border: #88cba4;\r\n --toast-success-text: #195c38;\r\n --toast-success-icon: #287a4a;\r\n --toast-info-bg: #edf4ff;\r\n --toast-info-border: #9bbcf3;\r\n --toast-info-text: #214c8c;\r\n --toast-info-icon: #356ec1;\r\n --toast-error-bg: #fff0ef;\r\n --toast-error-border: #edaaa5;\r\n --toast-error-text: #8a302b;\r\n --toast-error-icon: #bd4840;\r\n --toast-focus-ring: #2864d7;\r\n}\r\n[dft-data-toast-stack].is-dark {\r\n --toast-success-bg: #19392b;\r\n --toast-success-border: #3d9164;\r\n --toast-success-text: #d8f2e2;\r\n --toast-success-icon: #55b87c;\r\n --toast-info-bg: #1d3049;\r\n --toast-info-border: #456da6;\r\n --toast-info-text: #dce9ff;\r\n --toast-info-icon: #7ca9ed;\r\n --toast-error-bg: #452624;\r\n --toast-error-border: #a34f49;\r\n --toast-error-text: #ffe2df;\r\n --toast-error-icon: #ef837a;\r\n --toast-focus-ring: #82aaff;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-item] {\r\n align-items: flex-start;\r\n border: 1px solid var(--toast-info-border);\r\n border-radius: 10px;\r\n background: var(--toast-info-bg);\r\n box-shadow: 0 16px 42px rgba(9, 30, 66, .28);\r\n color: var(--toast-info-text);\r\n display: flex;\r\n gap: 12px;\r\n padding: 14px 16px 30px;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-item].is-success {\r\n background: var(--toast-success-bg);\r\n border-color: var(--toast-success-border);\r\n color: var(--toast-success-text);\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-item].is-info {\r\n background: var(--toast-info-bg);\r\n border-color: var(--toast-info-border);\r\n color: var(--toast-info-text);\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-item].is-error {\r\n background: var(--toast-error-bg);\r\n border-color: var(--toast-error-border);\r\n color: var(--toast-error-text);\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-icon] {\r\n align-items: center;\r\n background: var(--toast-info-icon);\r\n border-radius: 50%;\r\n color: #fff;\r\n display: inline-flex;\r\n flex: 0 0 24px;\r\n font-size: 14px;\r\n font-weight: 800;\r\n height: 24px;\r\n justify-content: center;\r\n line-height: 1;\r\n width: 24px;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-item].is-success [dft-data-toast-icon] { background: var(--toast-success-icon); }\r\n[dft-data-toast-stack] [dft-data-toast-item].is-info [dft-data-toast-icon] { background: var(--toast-info-icon); }\r\n[dft-data-toast-stack] [dft-data-toast-item].is-error [dft-data-toast-icon] { background: var(--toast-error-icon); }\r\n[dft-data-toast-stack] [dft-data-toast-copy] { display: flex; flex: 1; flex-direction: column; gap: 2px; min-width: 0; }\r\n[dft-data-toast-stack] [dft-data-toast-title] { font-size: 13px; }\r\n[dft-data-toast-stack] [dft-data-toast-message] {\r\n color: inherit;\r\n font-size: 12px;\r\n line-height: 1.45;\r\n overflow-wrap: anywhere;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-time] {\r\n bottom: 8px;\r\n color: inherit;\r\n font-size: 10px;\r\n line-height: 1;\r\n opacity: .78;\r\n position: absolute;\r\n right: 12px;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-close] {\r\n background: transparent;\r\n border: 0;\r\n border-radius: 5px;\r\n color: inherit;\r\n cursor: pointer;\r\n flex: 0 0 auto;\r\n font-size: 20px;\r\n height: 24px;\r\n line-height: 1;\r\n padding: 0 4px;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-close]:hover {\r\n background: color-mix(in srgb, currentColor 12%, transparent);\r\n color: inherit;\r\n}\r\n[dft-data-toast-stack] [dft-data-toast-close]:focus-visible {\r\n outline: 2px solid var(--toast-focus-ring);\r\n outline-offset: 2px;\r\n}\r\n";
// 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.");
}
const runtimeVariables = `
#confluence-plus-root {
--confluence-plus-ui-min-width: ${config.ui.minWidth}px;
--confluence-plus-ui-min-height: ${config.ui.minHeight}px;
}
`;
applyStyle(`${styles_default}
${toast_default}
${runtimeVariables}`);
}
// src/plugins/confluence/entry.js
installStyles({ config: Config });
installSharedTooltips();
bootstrap();
})();