MediaWiki:NewsForm.js

MediaWiki interface page

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
// Check if we are adding or editing news
if ( location.href.includes('News') && ( mw.config.get('wgAction') === 'formedit' || location.href.includes('Special:FormEdit/News') )) {
	var elements = Array.from(document.querySelectorAll('tbody th'));
	var keywordTr = elements.find(function (el) {return el.textContent.includes('SEO Keywords');}).closest('tr');
	var keywordTd = keywordTr.querySelector('td');
	var title = mw.config.get('wgTitle');

	var warningEl = function(text){
		var warning = document.createElement('span');
		warning.classList.add('seo-warning');
		warning.style.textAlign = 'center';
		warning.style.color = 'red';
		warning.style.position = 'relative';
		warning.style.zIndex = 11;
		warning.textContent = text;
		return warning;
	};
	
	// SEO Title check
	var seoTitle = document.querySelector('[name="News[seo_title]"');
	var seoTitleTd = seoTitle.closest('td');
	var checkTitleLength = function() {
		var titleWarning = seoTitleTd.parentElement.querySelector('.seo-warning');
		var titleLength = seoTitle.value.length;
		// If SEO Title too short
		if ( titleLength < 43  ) {
			if ( titleWarning ) { titleWarning.textContent = 'Title too short'; return;}
			seoTitleTd.parentNode.insertBefore(warningEl('Title too short'), seoTitleTd.nextSibling);
		} else if ( titleLength > 59 ) {
			if ( titleWarning ) { titleWarning.textContent = 'Title too long'; return;}
			seoTitleTd.parentNode.insertBefore(warningEl('Title too long'), seoTitleTd.nextSibling);
		} else if ( titleWarning ) {
			titleWarning.remove();
		}
	};
	seoTitle.onkeyup = checkTitleLength;
	// Check initial Title length
	checkTitleLength();
	
	// SEO Description check
	var seoDescription = document.querySelector('[name="News[seo_description]"');
	var seoDescriptionTd = seoDescription.closest('td');
	var checkDescriptionLength = function() {
		if ( seoDescription.value.length == 0 ) return;
		var descriptionWarning = seoDescriptionTd.parentElement.querySelector('.seo-warning');
		var descriptionLength = seoDescription.value.length;
		var errorText = '';
		// If SEO Description too short
		if ( descriptionLength < 120 ) {
			errorText = 'Description too short';
		} else if ( descriptionLength > 155 ) {
			errorText = 'Description too long';
		}
		// Check if description contains keywords 
		if ( !Array.from(keywordTd.querySelectorAll('.select2-match-entire')).find(function(el) { return seoDescription.value.includes(el.textContent); } ) ) {
			if ( errorText.length > 0 ) errorText += '. Does not contain Keywords';
			else errorText = 'Does not contain Keywords';
		}
		
		if ( errorText.length > 0 ) {
			if ( descriptionWarning ) descriptionWarning.textContent = errorText;
			else  seoDescriptionTd.parentNode.insertBefore(warningEl(errorText), seoDescriptionTd.nextSibling);
		} else if ( descriptionWarning ) descriptionWarning.remove();
	};
	seoDescription.onkeyup = checkDescriptionLength;
	// Check initial Title length
	checkDescriptionLength();
	
	//Content length check
	mw.hook('wikiEditor.toolbarReady').add(function ($textarea) {
		var content = document.querySelector('[name="News[content]"');
		if ( !content ) return;
		var contentTd = content.closest('td');
		content.style.overflowY = 'scroll';
		var checkContentLength = function() {
			
			var value = content.value;
			if ( value.length == 0 ) return;
			var contentWarning = contentTd.parentElement.querySelector('.seo-warning');
			var contentWords = value.split(' ').length;
			var contentErrorText = '';
			// If SEO Description too short
			if ( contentWords < 300 ) {
				if ( contentErrorText.length > 0 ) contentErrorText += '. Content under 300 words';
				contentErrorText = 'Content under 300 words';
			} 
			// Check if content has internal links
			if ( !value.includes('[[') ) {
				if ( contentErrorText.length > 0 ) contentErrorText += '. No internal links found';
				else contentErrorText = 'No internal links founds';
			}
			
			// Check if first paragraph has keywords
			var firstP = value.split('\n\n')[0];
			if ( !Array.from(keywordTd.querySelectorAll('.select2-match-entire')).find(function(el) { return firstP.includes(el.textContent); } ) ) {
				if ( contentErrorText.length > 0 ) contentErrorText += '. First paragraph does not include Keywords';
				else contentErrorText = 'First paragraph does not include Keywords';
			}

			if ( contentErrorText.length > 0 ) {
				if ( contentWarning ) contentWarning.textContent = contentErrorText;
				else  contentTd.parentNode.insertBefore(warningEl(contentErrorText), contentTd.nextSibling);
			} else if ( contentWarning ) contentWarning.remove();
		};
		content.onkeyup = checkContentLength;
		checkContentLength();
	});

  var keywordCheck = function (checkList) {
    var keywordWarning = keywordTr.querySelector('.seo-warning');
    var list = Array.from(checkList || []);
    if ( !list.find(function (el) {return title.includes( el.querySelector('.select2-match-entire').textContent ); }) ) {
      if (keywordWarning) return;
      keywordTr.insertBefore(warningEl('No keyword is present in the URL'), keywordTd.nextSibling);
    } else if (keywordWarning) {
      keywordWarning.remove();
    }
  };

  // Set up observer to check for keyword changes
  var observer = new MutationObserver(function (mutations) {
    // Check each mutation that occurred
    mutations.forEach(function (mutation) {
      // Check if the mutation was an addition of a node
      if (
        mutation.target.nodeName === 'UL' &&
        mutation.addedNodes.length > 0 &&
        !mutation.addedNodes[0].classList.contains('select2-search') &&
        mutation.type === 'childList'
      ) {
        keywordCheck(mutation.addedNodes);
      }
    });
  });
  var config = { childList: true, subtree: true };
  observer.observe(keywordTd, config);
}
// Check if we're editing a page and if we are, then add news search button to wikieditor
if (
  ['edit', 'submit', 'formedit'].indexOf(mw.config.get('wgAction')) !== -1 ||
  location.href.includes('Special:FormEdit')
) {
  // Set up some variables for global use
  var caretPosition = 0;
  var lastKeyupTime = 0;
  var textarea;
  // Search function
  var search = function () {
    // Get the current timestamp
    var currentTime = new Date().getTime();

    // Check if at least 2 seconds have passed since the last keyup event
    if (currentTime - lastKeyupTime > 2000 || lastKeyupTime === 0) {
      var searchInput = document.getElementById('search-input');
      // Get the search phrase entered by the user
      var searchPhrase = searchInput.value.trim().toLowerCase() + '*';
      // Do nothing unless at least 4 letters are entered (5 with our manual * at the end)
      if (searchPhrase.length < 5) return;

      var suggestionsContainer = document.getElementById('suggestions');
      if (lastKeyupTime === 0) {
        suggestionsContainer.addEventListener('click', function (e) {
          if (e.target.classList.contains('newsForm-suggestion')) {
            searchInput.value = e.target.textContent;
          }
        });
      }
      // Clear any existing suggestions
      suggestionsContainer.innerHTML = '';
      var url = 'api.php';
      params = new URLSearchParams({
        action: 'query',
        list: 'search',
        srsearch: searchPhrase,
        format: 'json',
        srlimit: 5,
        // origin: location.origin
        origin: '*'
      });

      fetch(url + '?' + params)
        .then(function (response) {
          return response.json();
        })
        .then(function (response) {
          // Loop through the suggestions and add matching items to the container
          var results = response.query.search;
          for (var i = 0; i < results.length; i++) {
            var result = results[i];
            var suggestionItem = document.createElement('div');
            suggestionItem.innerText = result.title;
            suggestionItem.classList.add('newsForm-suggestion');
            suggestionsContainer.appendChild(suggestionItem);
          }
        })
        .catch(function (error) {
          console.log(error);
        });

      lastKeyupTime = currentTime;
    }
  };
  // Create the modal for embeds
  var embedModal = document.createElement('div');
  embedModal.id = 'embedModal';
  embedModal.classList.add('newsForm');
  
  var select = document.createElement('select');
var options = {
  '': '',
  'Twitter': 'Twitter',
  'Youtube': 'Youtube',
  'Instagram': 'Instagram',
  'Reddit': 'Reddit',
  'Linkedin': 'Linkedin',
  'Twitch': 'Twitch',
  'TwitchClip': 'TwitchClip',
  'Kick': 'Kick',
  'KickClip': 'KickClip',
  'Tiktok': 'Tiktok',
  'Facebook': 'Facebook'
};

// Add options to the select element
for (var key in options) {
  var option = document.createElement('option');
  option.value = options[key];
  option.text = key;
  select.appendChild(option);
}

var appendToForm = function(form, title, name){
	var fieldTitle = document.createElement('div');
	fieldTitle.innerHTML = title;
	var fieldName = document.createElement('input');
	fieldName.name = name;
	form.append(fieldTitle, fieldName);
};

// Add event listener for value change
select.addEventListener('change', function() {
	var selectedValue = select.value;
	var form = document.getElementById('embedForm');
	form.innerHTML = '';
	
	switch (selectedValue) {
	case 'Twitter':
	  appendToForm(form, 'URL', 'url');
	  appendToForm(form, 'Width', 'w');
	  break;
	case 'Youtube':
	  appendToForm(form, 'ID', 'id');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  appendToForm(form, 'Start time in seconds', 'start');
	  break;
	case 'Instagram':
	  appendToForm(form, 'URL', 'url');
	  appendToForm(form, 'Width', 'w');
	  break;
	case 'Reddit':
	  appendToForm(form, 'URL', 'url');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  break;
	case 'Linkedin':
	  appendToForm(form, 'ID', 'id');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  appendToForm(form, 'UGC', 'ugc');
	  break;
	case 'Twitch':
	  appendToForm(form, 'Channel name', 'id');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  break;
	case 'TwitchClip':
	  appendToForm(form, 'ID', 'id');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  break;
	case 'Kick':
	  appendToForm(form, 'Channel name', 'id');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  break;
	case 'KickClip':
	  appendToForm(form, 'Channel name', 'channel');
	  appendToForm(form, 'Clip ID', 'id');
	  appendToForm(form, 'Width', 'w');
	  appendToForm(form, 'Height', 'h');
	  break;
	case 'Tiktok':
	  appendToForm(form, 'ID', 'id');
	  break;
	case 'Facebook':
	  appendToForm(form, 'URL', 'url');
	  appendToForm(form, 'Width', 'w');
	  break;
	default:
	  break;
	}
});


  var embedContent = document.createElement('div');
  embedContent.classList.add('newsForm-content');
  var embedClose = document.createElement('span');
  embedClose.classList.add('newsForm-close');
  embedClose.innerHTML = '&times';
  var embedH2 = document.createElement('h2');
  embedH2.innerHTML = 'Embed';
  var embedBody = document.createElement('form');
  embedBody.id = 'embedForm';

  var embedInsert = document.createElement('button');
  embedInsert.innerHTML = 'Insert embed';
  
  embedInsert.addEventListener('click', function () {
    var value = textarea.value;
    var selectEl = document.querySelector('#embedModal select');
    var selectValue = selectEl.value;
    var formInputs = document.querySelectorAll('#embedForm input');
    var embed = '{{' + selectValue;
    formInputs.forEach(function (input){
	  var inputValue = input.value;
	  var inputName = input.name;
	  if ( inputValue !== '' ) {
	  	embed += '|' + inputName + '=' + inputValue;
	  }
	});
    embed += '}}';

    textarea.value = value.substring(0, caretPosition) + embed + value.substring(caretPosition);
    document.querySelector('#embedForm').innerHTML = '';
    selectEl.value = '';
    embedModal.style.display = 'none';
  });

  embedClose.onclick = function () {
    embedModal.style.display = 'none';
  };
  embedContent.append(embedClose, embedH2, select, embedBody, embedInsert);
  embedModal.append(embedContent);
  // Append the modal HTML to the body element
  document.body.appendChild(embedModal);
  
  // Create the modal for search
  var modal = document.createElement('div');
  modal.id = 'newsForm';
  modal.classList.add('newsForm');

  var content = document.createElement('div');
  content.classList.add('newsForm-content');
  var close = document.createElement('span');
  close.classList.add('newsForm-close');
  close.innerHTML = '&times';
  var h2 = document.createElement('h2');
  h2.innerHTML = 'Search';
  var p = document.createElement('div');
  p.innerHTML = 'Enter your search phrase:';
  var input = document.createElement('input');
  input.id = 'search-input';
  var p2 = document.createElement('div');
  p2.innerHTML = 'Results:';
  var suggestions = document.createElement('div');
  suggestions.id = 'suggestions';

  var p3 = document.createElement('div');
  p3.innerHTML = 'Displayed title';
  var input2 = document.createElement('input');
  input2.id = 'display-title';

  var insert = document.createElement('button');
  insert.innerHTML = 'Insert link';

  insert.addEventListener('click', function () {
    var value = textarea.value;
    var link = '[[' + input.value + '|' + input2.value + ']]';
    textarea.value =
      value.substring(0, caretPosition) + link + value.substring(caretPosition);
    input.value = '';
    input2.value = '';
    modal.style.display = 'none';
  });

  close.onclick = function () {
    modal.style.display = 'none';
  };
  input.addEventListener('keyup', search);
  content.append(close, h2, p, input, p3, input2, p2, suggestions, insert);
  modal.append(content);
  // Append the modal HTML to the body element
  document.body.appendChild(modal);

  // Add a hook handler.
  mw.hook('wikiEditor.toolbarReady').add(function ($textarea) {
    importStylesheet('MediaWiki:NewsForm.css');
    // Configure a new toolbar entry on the given $textarea jQuery object.
    $textarea.wikiEditor('addToToolbar', {
      section: 'secondary',
      group: 'default',
      tools: {
        dothing: {
          type: 'element',
          element: function (context) {
            // Note that the `context` object contains various useful references.
            var button = new OO.ui.ButtonInputWidget({
              label: '',
              icon: 'articlesSearch'
            });

            textarea = document.querySelector('.wikiEditor-ui-text textarea');

            // var wikieditorInstance = $( '.wikiEditor-ui-text textarea' ).wikieditor();
            button.connect(null, {
              click: function (e) {
                modal.style.display = 'flex';
                caretPosition = textarea.selectionStart;
              }
            });
            return button.$element;
          }
        }
      }
    });
    
    $textarea.wikiEditor('addToToolbar', {
      section: 'secondary',
      group: 'default',
      tools: {
        dothing: {
          type: 'element',
          element: function (context) {
            // Note that the `context` object contains various useful references.
            var button = new OO.ui.ButtonInputWidget({
              icon: 'book'
            });

            textarea = document.querySelector('.wikiEditor-ui-text textarea');

            button.connect(null, {
              click: function (e) {
                embedModal.style.display = 'flex';
                caretPosition = textarea.selectionStart;
              }
            });
            return button.$element;
          }
        }
      }
    });
  });
}