Jump to content
Wikipedia The Free Encyclopedia

User:Polygnotus/Scripts/VEbuttons.js

From Wikipedia, the free encyclopedia
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
Documentation for this user script can be added at User:Polygnotus/Scripts/VEbuttons.
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
 // Add custom buttons to Wikipedia's Visual Editor based on JSON configuration
 // Add this code to your common.js page on Wikipedia
 // (e.g., https://en.wikipedia.org/wiki/User:YourUsername/common.js)

 (function(){
 // Wait for the VisualEditor to be ready with more dependencies to ensure we have access to all needed components
 mw.loader.using([
 'ext.visualEditor.desktopArticleTarget',
 'ext.visualEditor.core',
 'oojs-ui',
 'mediawiki.api'
 ]).then(function(){
 console.log('VE Button Script: Dependencies loaded');

 // Better activation hooks that catch both initial load and editor reactivation
 mw.hook('ve.activationComplete').add(initCustomButtons);

 // Also hook into surface ready events which fire when switching between VE and source mode
 if(typeofOO!=='undefined'&&OO.ui){
 // Wait for OO.ui to be fully initialized
 $(function(){
 if(ve.init&&ve.init.target){
 ve.init.target.on('surfaceReady',function(){
 console.log('VE Button Script: Surface ready event triggered');
 initCustomButtons();
 });
 }
 });
 }

 functioninitCustomButtons(){
 console.log('VE Button Script: Initializing custom buttons');
 loadButtonsConfig().then(function(buttons){
 if(Array.isArray(buttons)&&buttons.length>0){
 console.log('VE Button Script: Loaded '+buttons.length+' button configs');

 // Register all tools and commands
 buttons.forEach(registerButtonTool);

 // Add timeout to ensure toolbar is fully initialized
 setTimeout(function(){
 addCustomToolbarGroup(buttons);
 },500);
 }else{
 console.log('VE Button Script: No button configurations found or empty array');
 }
 }).catch(function(error){
 console.error('VE Button Script: Failed to load custom buttons configuration:',error);
 });
 }

 // Load buttons configuration from user's JSON page with better error handling
 asyncfunctionloadButtonsConfig(){
 console.log('VE Button Script: Attempting to load button configuration');
 constusername=mw.config.get('wgUserName');
 if(!username){
 console.log('VE Button Script: No username found, cannot load configuration');
 return[];
 }

 constapi=newmw.Api();
 try{
 // For testing/debugging, we can check if we have a hardcoded config in local storage
 constdebugConfig=localStorage.getItem('veButtonsDebugConfig');
 if(debugConfig){
 console.log('VE Button Script: Using debug configuration from localStorage');
 try{
 returnJSON.parse(debugConfig);
 }catch(e){
 console.error('VE Button Script: Invalid debug configuration:',e);
 }
 }

 console.log(`VE Button Script: Fetching configuration from User:${username}/VEbuttonsJSON.json`);
 constresult=awaitapi.get({
 action:'query',
 prop:'revisions',
 titles:`User:${username}/VEbuttonsJSON.json`,
 rvslots:'*',
 rvprop:'content',
 formatversion:'2',
 uselang:'content',// Enable caching
 smaxage:'86400',// Cache for 1 day
 maxage:'86400'// Cache for 1 day
 });

 // Log the full API response for debugging
 console.log('VE Button Script: API response received',result);

 if(!result.query||!result.query.pages||!result.query.pages.length){
 console.log('VE Button Script: No pages returned from API');
 return[];
 }

 if(result.query.pages[0].missing){
 console.log('VE Button Script: Configuration page not found');
 return[];
 }

 if(!result.query.pages[0].revisions||!result.query.pages[0].revisions.length){
 console.log('VE Button Script: No revisions found for configuration page');
 return[];
 }

 constcontent=result.query.pages[0].revisions[0].slots.main.content;
 console.log('VE Button Script: Raw configuration content',content);

 // Use more robust JSON parsing
 try{
 constparsed=JSON.parse(content);
 console.log('VE Button Script: Configuration parsed successfully',parsed);
 returnparsed;
 }catch(jsonError){
 console.error('VE Button Script: JSON parsing error:',jsonError);
 return[];
 }
 }catch(error){
 console.error('VE Button Script: Error loading buttons configuration:',error);
 return[];
 }
 }

 // Register a button tool based on config
 functionregisterButtonTool(config){
 // Add custom icon CSS if URL is provided
 if(config.icon&&config.icon.startsWith('http')){
 addCustomIconCSS(config.name,config.icon);
 }

 // Create command
 constCommandClass=function(){
 ve.ui.Command.call(this,config.name);
 };
 OO.inheritClass(CommandClass,ve.ui.Command);

 CommandClass.prototype.execute=function(surface){
 try{
 constsurfaceModel=surface.getModel();

 letcontent=config.insertText;
 // Handle the string concatenation pattern found in the JSON
 if(typeofcontent==='string'){
 // This handles patterns like "text" + ":more" or "pre~~" + "~~post"
 content=content.replace(/'\s*\+\s*'/g,'');
 }

 surfaceModel.getFragment()
 .collapseToEnd()
 .insertContent(content)
 .collapseToEnd()
 .select();

 returntrue;
 }catch(error){
 console.error(`Error executing command ${config.name}:`,error);
 returnfalse;
 }
 };

 ve.ui.commandRegistry.register(newCommandClass());

 // Create tool
 constToolClass=function(){
 ve.ui.Tool.apply(this,arguments);
 };
 OO.inheritClass(ToolClass,ve.ui.Tool);

 ToolClass.static.name=config.name;
 ToolClass.static.title=config.title||config.name;
 ToolClass.static.commandName=config.name;
 ToolClass.static.icon=config.icon&&config.icon.startsWith('http')
 ?'custom-'+config.name
 :(config.icon||'help');

 ToolClass.prototype.onSelect=function(){
 this.setActive(false);
 this.getCommand().execute(this.toolbar.getSurface());
 };

 ToolClass.prototype.onUpdateState=function(){
 this.setActive(false);
 };

 ve.ui.toolFactory.register(ToolClass);
 }

 // Add custom CSS for icons
 functionaddCustomIconCSS(name,iconUrl){
 conststyleId=`custom-icon-${name}`;
 if(!document.getElementById(styleId)){
 conststyle=document.createElement('style');
 style.id=styleId;
 style.textContent=`
  .oo-ui-icon-custom-${name} {
  background-image: url(${iconUrl}) !important;
  background-size: contain !important;
  background-position: center !important;
  background-repeat: no-repeat !important;
  }
  `;
 document.head.appendChild(style);
 }
 }

 // Add a custom toolbar group with our buttons - improved with better error handling and jQuery fallback
 functionaddCustomToolbarGroup(buttons){
 console.log('VE Button Script: Attempting to add custom toolbar group');

 if(!ve.init.target){
 console.warn('VE Button Script: Visual editor target not found');
 return;
 }

 if(!ve.init.target.toolbar){
 console.warn('VE Button Script: Visual editor toolbar not found');
 return;
 }

 // Get button names for the group
 constbuttonNames=buttons.map(config=>config.name);
 console.log('VE Button Script: Button names for toolbar group:',buttonNames);

 // Check if OO and ve.ui are properly defined
 if(!OO||!ve.ui||!ve.ui.ToolGroup){
 console.error('VE Button Script: Required OOUI components are not available');
 tryJQueryFallback(buttons);
 return;
 }

 // First, ensure our custom toolbar group class is defined
 // Important: Only define it once to avoid errors
 if(!ve.ui.CustomToolbarGroup){
 try{
 console.log('VE Button Script: Defining CustomToolbarGroup class');

 // Define a custom toolbar group
 ve.ui.CustomToolbarGroup=functionVeUiCustomToolbarGroup(toolFactory,config){
 // Ensure this is being called as a constructor
 if(!(thisinstanceofVeUiCustomToolbarGroup)){
 returnnewVeUiCustomToolbarGroup(toolFactory,config);
 }

 // Call parent constructor
 ve.ui.ToolGroup.call(this,toolFactory,config);
 };

 // Safe inheritance with fallbacks
 if(OO.inheritClass){
 OO.inheritClass(ve.ui.CustomToolbarGroup,ve.ui.BarToolGroup);
 }else{
 // Fallback inheritance if OO.inheritClass is not available
 ve.ui.CustomToolbarGroup.prototype=Object.create(ve.ui.BarToolGroup.prototype);
 ve.ui.CustomToolbarGroup.prototype.constructor=ve.ui.CustomToolbarGroup;
 // Copy static properties
 if(ve.ui.BarToolGroup.static){
 ve.ui.CustomToolbarGroup.static=Object.assign({},ve.ui.BarToolGroup.static);
 }else{
 ve.ui.CustomToolbarGroup.static={};
 }
 }

 ve.ui.CustomToolbarGroup.static.name='customTools';
 ve.ui.CustomToolbarGroup.static.title='Custom tools';

 // Register the toolbar group
 if(ve.ui.toolGroupFactory&&typeofve.ui.toolGroupFactory.register==='function'){
 ve.ui.toolGroupFactory.register(ve.ui.CustomToolbarGroup);
 console.log('VE Button Script: CustomToolbarGroup registered successfully');
 }else{
 console.error('VE Button Script: toolGroupFactory not available');
 thrownewError('toolGroupFactory not available');
 }
 }catch(error){
 console.error('VE Button Script: Error defining CustomToolbarGroup:',error);
 tryJQueryFallback(buttons);
 return;
 }
 }

 // Add the group to the toolbar
 consttoolbar=ve.init.target.toolbar;

 // Only add if the group doesn't exist yet
 try{
 if(!toolbar.getToolGroupByName||!toolbar.getToolGroupByName('customTools')){
 console.log('VE Button Script: Adding customTools group to toolbar');

 // Get the target index to insert the group
 lettargetIndex=-1;

 if(toolbar.items&&toolbar.items.length){
 // Safer way to find insertion point using items collection
 for(leti=0;i<toolbar.items.length;i++){
 constgroup=toolbar.items[i];
 if(group.name==='format'||group.name==='structure'){
 targetIndex=i+1;
 break;
 }
 }
 }elseif(toolbar.getToolGroups&&typeoftoolbar.getToolGroups==='function'){
 // Legacy way
 consttoolGroups=toolbar.getToolGroups();
 for(leti=0;i<toolGroups.length;i++){
 constgroup=toolGroups[i];
 if(group.name==='format'||group.name==='structure'){
 targetIndex=i+1;
 break;
 }
 }
 }

 console.log('VE Button Script: Target index for insertion:',targetIndex);

 // Create the group config
 constgroupConfig={
 name:'customTools',
 type:'customTools',
 include:buttonNames,
 label:'Custom'
 };

 // Add the group at the desired position
 if(toolbar.getItems&&toolbar.getItems()[0]&&toolbar.getItems()[0].addItems){
 // Standard method
 if(targetIndex!==-1){
 console.log('VE Button Script: Adding at specific position',targetIndex);
 toolbar.getItems()[0].addItems([groupConfig],targetIndex);
 }else{
 console.log('VE Button Script: Adding at end of toolbar');
 toolbar.getItems()[0].addItems([groupConfig]);
 }

 // Rebuild the toolbar to show the new group
 console.log('VE Button Script: Rebuilding toolbar');
 toolbar.rebuild();
 }elseif(toolbar.setup&&typeoftoolbar.setup==='function'){
 // Alternative method for some VE versions
 console.log('VE Button Script: Using toolbar.setup method');
 consttoolbarConfig=toolbar.getDefaultConfig();
 toolbarConfig.push(groupConfig);
 toolbar.setup(toolbarConfig);
 }else{
 // Last resort - try using jQuery to manually append our buttons
 console.log('VE Button Script: Using jQuery fallback for toolbar manipulation');
 tryJQueryFallback(buttons);
 }
 }else{
 console.log('VE Button Script: customTools group already exists');
 }
 }catch(error){
 console.error('VE Button Script: Error adding toolbar group:',error);
 // Try jQuery fallback if the normal methods fail
 tryJQueryFallback(buttons);
 }
 }

 // jQuery fallback method for when the normal VE integration fails
 functiontryJQueryFallback(buttons){
 console.log('VE Button Script: Attempting jQuery fallback for button insertion');

 // Wait a moment to ensure the UI is stable
 setTimeout(function(){
 try{
 // Create a proper new group for our buttons
 const$toolGroup=$('<div>')
 .addClass('ve-ui-toolbar-group-custom oo-ui-widget oo-ui-toolGroup oo-ui-barToolGroup oo-ui-widget-enabled')
 .attr('title','Custom Tools');

 const$toolsContainer=$('<div>')
 .addClass('oo-ui-toolGroup-tools oo-ui-barToolGroup-tools oo-ui-toolGroup-enabled-tools')
 .appendTo($toolGroup);

 // Add each button
 buttons.forEach(function(config){
 const$button=$('<span>')
 .addClass('oo-ui-widget oo-ui-iconElement oo-ui-tool-with-icon oo-ui-tool oo-ui-tool-name-'+config.name+' oo-ui-widget-enabled')
 .appendTo($toolsContainer);

 const$link=$('<a>')
 .addClass('oo-ui-tool-link')
 .attr('role','button')
 .attr('tabindex','0')
 .attr('title',config.title||config.name)
 .appendTo($button);

 // Add the icon structure
 $('<span>')
 .addClass('oo-ui-tool-checkIcon oo-ui-widget oo-ui-widget-enabled oo-ui-iconElement oo-ui-iconElement-icon oo-ui-icon-check oo-ui-labelElement-invisible oo-ui-iconWidget')
 .appendTo($link);

 if(config.icon){
 if(config.icon.startsWith('http')){
 // Custom icon handling
 $('<span>')
 .addClass('oo-ui-iconElement-icon custom-'+config.name)
 .css({
 'background-image':'url('+config.icon+')',
 'background-size':'contain',
 'background-position':'center',
 'background-repeat':'no-repeat'
 })
 .appendTo($link);
 }else{
 $('<span>')
 .addClass('oo-ui-iconElement-icon oo-ui-icon-'+config.icon)
 .appendTo($link);
 }
 }else{
 $('<span>')
 .addClass('oo-ui-iconElement-icon oo-ui-icon-help')
 .appendTo($link);
 }

 $('<span>')
 .addClass('oo-ui-tool-title')
 .text(config.title||config.name)
 .appendTo($link);

 // Add click event handler
 $button.on('click',function(e){
 e.preventDefault();
 e.stopPropagation();
 try{
 constsurface=ve.init.target.getSurface();
 constsurfaceModel=surface.getModel();

 letcontent=config.insertText;
 // Handle the concatenation pattern with single quotes
 if(typeofcontent==='string'){
 content=content.replace(/'\s*\+\s*'/g,'');
 }

 surfaceModel.getFragment()
 .collapseToEnd()
 .insertContent(content)
 .collapseToEnd()
 .select();
 }catch(error){
 console.error('VE Button Script: Error executing button action:',error);
 }
 });
 });

 // Insert our group at an appropriate location in the toolbar
 var$insertPosition=$('.ve-ui-toolbar-group-structure, .ve-ui-toolbar-group-format').last();

 if($insertPosition.length){
 $toolGroup.insertAfter($insertPosition);
 console.log('VE Button Script: jQuery fallback - button group added successfully after',$insertPosition.attr('class'));
 }else{
 // Fallback: add to main toolbar
 $('.oo-ui-toolbar-tools').first().append($toolGroup);
 console.log('VE Button Script: jQuery fallback - button group added to main toolbar');
 }
 }catch(error){
 console.error('VE Button Script: jQuery fallback failed:',error);
 }
 },1000);
 }
 });
 })();

AltStyle によって変換されたページ (->オリジナル) /