MediaWiki:Common.js: различия между версиями

Материал из Wikibrand
(Поиск: подсказки с изображениями)
(Fix search suggestions init: wait for mediawiki.util before thumbnails dropdown bootstrap)
 
Строка 14: Строка 14:
}
}


mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
// Wikibrand: custom search suggestions with thumbnails in header search
// Wikibrand: custom search suggestions with thumbnails in header search
( function () {
( function () {
Строка 264: Строка 265:
   }
   }
}() );
}() );
} );

Текущая версия от 18:04, 11 марта 2026

if ( typeof mw !== 'undefined' && !mw.config.get( 'wgUserName' ) ) {
  mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
    var action = mw.util.getParamValue( 'action' );
    var isMissing = mw.config.get( 'wgArticleId' ) === 0 || mw.config.get( 'wgCurRevisionId' ) === 0;
    var isSpecial = mw.config.get( 'wgCanonicalNamespace' ) === 'Special';
    if ( action === 'edit' && isMissing && !isSpecial ) {
      var target = mw.config.get( 'wgPageName' );
      var redirectUrl = mw.util.getUrl( 'Special:BrandRequest', { target: target } );
      if ( window.location.href.indexOf( 'Special:BrandRequest' ) === -1 ) {
        window.location.replace( redirectUrl );
      }
    }
  } );
}

mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
// Wikibrand: custom search suggestions with thumbnails in header search
( function () {
  if ( typeof mw === 'undefined' || typeof fetch === 'undefined' ) {
    return;
  }

  var API = mw.util.wikiScript( 'api' );
  var imgCache = Object.create( null );

  function addStyles() {
    if ( document.getElementById( 'wbg-search-style' ) ) {
      return;
    }
    var style = document.createElement( 'style' );
    style.id = 'wbg-search-style';
    style.textContent = '' +
      '.wbg-search-form{position:relative!important;}' +
      '.wbg-search-panel{position:absolute;left:0;right:0;top:calc(100% + 2px);z-index:1200;background:#fff;border:1px solid #a2a9b1;box-shadow:0 4px 14px rgba(0,0,0,.12);max-height:420px;overflow:auto;}' +
      '.wbg-search-item{display:flex;align-items:center;gap:10px;padding:8px 10px;text-decoration:none;color:#202122;border-top:1px solid #eaecf0;}' +
      '.wbg-search-item:first-child{border-top:none;}' +
      '.wbg-search-item:hover{background:#f8f9fa;}' +
      '.wbg-search-thumb{width:38px;height:38px;border-radius:4px;object-fit:cover;flex:0 0 38px;background:#eaecf0;}' +
      '.wbg-search-fallback{width:38px;height:38px;border-radius:4px;display:flex;align-items:center;justify-content:center;background:#eaecf0;color:#54595d;font-weight:700;font-size:12px;flex:0 0 38px;}' +
      '.wbg-search-title{font-size:14px;line-height:1.25;}' +
      '.wbg-search-meta{font-size:12px;color:#72777d;margin-top:2px;}' +
      '.wbg-search-form.wbg-search-active .cdx-menu,.wbg-search-form.wbg-search-active .suggestions-results{display:none!important;}';
    document.head.appendChild( style );
  }

  function apiQuery( params ) {
    params.format = 'json';
    var qs = new URLSearchParams( params );
    return fetch( API + '?' + qs.toString(), { credentials: 'same-origin' } ).then( function ( r ) {
      return r.json();
    } );
  }

  function firstPage( data ) {
    if ( !data || !data.query || !data.query.pages ) {
      return null;
    }
    var pages = data.query.pages;
    for ( var k in pages ) {
      if ( Object.prototype.hasOwnProperty.call( pages, k ) ) {
        return pages[ k ];
      }
    }
    return null;
  }

  function firstLetter( title ) {
    if ( !title ) {
      return '?';
    }
    return title.charAt( 0 ).toUpperCase();
  }

  function getImageForTitle( title ) {
    if ( Object.prototype.hasOwnProperty.call( imgCache, title ) ) {
      return Promise.resolve( imgCache[ title ] );
    }

    return apiQuery( {
      action: 'query',
      titles: title,
      prop: 'images',
      imlimit: '1'
    } ).then( function ( data1 ) {
      var p = firstPage( data1 );
      var imgTitle = p && p.images && p.images[ 0 ] && p.images[ 0 ].title;
      if ( !imgTitle ) {
        imgCache[ title ] = null;
        return null;
      }

      return apiQuery( {
        action: 'query',
        titles: imgTitle,
        prop: 'imageinfo',
        iiprop: 'url',
        iiurlwidth: '76'
      } ).then( function ( data2 ) {
        var fp = firstPage( data2 );
        var ii = fp && fp.imageinfo && fp.imageinfo[ 0 ];
        var url = ii && ( ii.thumburl || ii.url ) ? ( ii.thumburl || ii.url ) : null;
        imgCache[ title ] = url;
        return url;
      } );
    } ).catch( function () {
      imgCache[ title ] = null;
      return null;
    } );
  }

  function buildItemNode( item ) {
    var a = document.createElement( 'a' );
    a.className = 'wbg-search-item';
    a.href = item.fullurl || mw.util.getUrl( item.title );

    if ( item.thumb ) {
      var img = document.createElement( 'img' );
      img.className = 'wbg-search-thumb';
      img.src = item.thumb;
      img.alt = item.title;
      img.loading = 'lazy';
      a.appendChild( img );
    } else {
      var fb = document.createElement( 'div' );
      fb.className = 'wbg-search-fallback';
      fb.textContent = firstLetter( item.title );
      a.appendChild( fb );
    }

    var textWrap = document.createElement( 'div' );
    var t = document.createElement( 'div' );
    t.className = 'wbg-search-title';
    t.textContent = item.title;
    textWrap.appendChild( t );

    var m = document.createElement( 'div' );
    m.className = 'wbg-search-meta';
    m.textContent = 'Открыть статью';
    textWrap.appendChild( m );

    a.appendChild( textWrap );
    return a;
  }

  function debounce( fn, ms ) {
    var timer = 0;
    return function () {
      var args = arguments;
      clearTimeout( timer );
      timer = window.setTimeout( function () {
        fn.apply( null, args );
      }, ms );
    };
  }

  function attachToInput( input ) {
    var form = input.closest( 'form' ) || input.parentElement;
    if ( !form || form.dataset.wbgSearchReady === '1' ) {
      return;
    }
    form.dataset.wbgSearchReady = '1';
    form.classList.add( 'wbg-search-form' );

    var panel = document.createElement( 'div' );
    panel.className = 'wbg-search-panel';
    panel.style.display = 'none';
    form.appendChild( panel );

    var lastReq = 0;

    function hidePanel() {
      panel.style.display = 'none';
      panel.innerHTML = '';
      form.classList.remove( 'wbg-search-active' );
    }

    function renderItems( list ) {
      panel.innerHTML = '';
      if ( !list.length ) {
        hidePanel();
        return;
      }
      list.forEach( function ( item ) {
        panel.appendChild( buildItemNode( item ) );
      } );
      panel.style.display = 'block';
      form.classList.add( 'wbg-search-active' );
    }

    var runSearch = debounce( function () {
      var q = ( input.value || '' ).trim();
      if ( q.length < 2 ) {
        hidePanel();
        return;
      }

      lastReq += 1;
      var reqId = lastReq;

      apiQuery( {
        action: 'query',
        generator: 'prefixsearch',
        gpssearch: q,
        gpslimit: '8',
        prop: 'info',
        inprop: 'url'
      } ).then( function ( data ) {
        if ( reqId !== lastReq ) {
          return;
        }

        var pagesObj = data && data.query && data.query.pages ? data.query.pages : {};
        var pages = Object.keys( pagesObj ).map( function ( k ) {
          return pagesObj[ k ];
        } ).sort( function ( a, b ) {
          return ( a.index || 0 ) - ( b.index || 0 );
        } );

        return Promise.all( pages.map( function ( p ) {
          return getImageForTitle( p.title ).then( function ( thumb ) {
            return {
              title: p.title,
              fullurl: p.fullurl,
              thumb: thumb
            };
          } );
        } ) );
      } ).then( function ( items ) {
        if ( reqId !== lastReq || !items ) {
          return;
        }
        renderItems( items );
      } ).catch( function () {
        hidePanel();
      } );
    }, 180 );

    input.addEventListener( 'input', runSearch );
    input.addEventListener( 'focus', runSearch );
    input.addEventListener( 'keydown', function ( e ) {
      if ( e.key === 'Escape' ) {
        hidePanel();
      }
    } );

    document.addEventListener( 'click', function ( e ) {
      if ( !form.contains( e.target ) ) {
        hidePanel();
      }
    } );
  }

  function init() {
    addStyles();
    var inputs = document.querySelectorAll( 'input#searchInput, input[name="search"]' );
    for ( var i = 0; i < inputs.length; i++ ) {
      attachToInput( inputs[ i ] );
    }
  }

  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', init );
  } else {
    init();
  }
}() );
} );