gadget-Morebits.js

Trang này không có sẵn trong ngôn ngữ khác.

//  /**  * morebits.js  * ===========  * A library full of lots of goodness for user scripts on MediaWiki wikis, including Wiki Tiếng Việt.  *  * The highlights include:  *   - Morebits.quickForm class - generates quick HTML forms on the fly  *   - Morebits.wiki.api class - makes calls to the MediaWiki API  *   - Morebits.wiki.page class - modifies pages on the wiki (edit, revert, delete, etc.)  *   - Morebits.wikitext class - contains some utilities for dealing with wikitext  *   - Morebits.status class - a rough-and-ready status message displayer, used by the Morebits.wiki classes  *   - Morebits.simpleWindow class - a wrapper for jQuery UI Dialog with a custom look and extra features  *  * Dependencies:  *   - The whole thing relies on jQuery.  But most wikis should provide this by default.  *   - Morebits.quickForm, Morebits.simpleWindow, and Morebits.status rely on the "morebits.css" file for their styling.  *   - Morebits.simpleWindow and Morebits.quickForm tooltips rely on jquery UI Dialog (from ResourceLoader module name 'jquery.ui').  *   - To create a gadget based on morebits.js, use this syntax in MediaWiki:Gadgets-definition:  *       * GadgetName[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,mediawiki.Title,jquery.ui]|morebits.js|morebits.css|GadgetName.js  *   - Alternatively, you can configure morebits.js as a hidden gadget in MediaWiki:Gadgets-definition:  *       * morebits[ResourceLoader|dependencies=mediawiki.user,mediawiki.util,mediawiki.Title,jquery.ui|hidden]|morebits.js|morebits.css  *     and then load ext.gadget.morebits as one of the dependencies for the new gadget  *  * All the stuff here works on all browsers for which MediaWiki provides JavaScript support.  *  * This library is maintained by the maintainers of Twinkle.  * For queries, suggestions, help, etc., head to [[Wikipedia talk:Twinkle]] on English Wikipedia [http://en.wikipedia.org].  * The latest development source is available at [https://github.com/azatoth/twinkle/blob/master/morebits.js].  */   (function (window, document, $) { // Wrap entire file with anonymous function  var Morebits = {}; window.Morebits = Morebits;  // allow global access    /**  * **************** Morebits.userIsInGroup() ****************  * Simple helper function to see what groups a user might belong  * @param {string} group  eg. `sysop`, `extendedconfirmed`, etc  * @returns {boolean}  */ Morebits.userIsInGroup = function (group) { return mw.config.get('wgUserGroups').indexOf(group) !== -1; }; // Used a lot Morebits.userIsSysop = Morebits.userIsInGroup('sysop');    /**  * **************** Morebits.sanitizeIPv6() ****************  * JavaScript translation of the MediaWiki core function IP::sanitizeIP() in  * includes/utils/IP.php.  * Converts an IPv6 address to the canonical form stored and used by MediaWiki.  * @param {string} address - The IPv6 address  * @returns {string}  */ Morebits.sanitizeIPv6 = function (address) { address = address.trim(); if (address === '') { return null; } if (!mw.util.isIPv6Address(address)) { return address; // nothing else to do for IPv4 addresses or invalid ones } // Remove any whitespaces, convert to upper case address = address.toUpperCase(); // Expand zero abbreviations var abbrevPos = address.indexOf('::'); if (abbrevPos > -1) { // We know this is valid IPv6. Find the last index of the // address before any CIDR number (e.g. "a:b:c::/24"). var CIDRStart = address.indexOf('/'); var addressEnd = CIDRStart > -1 ? CIDRStart - 1 : address.length - 1; // If the '::' is at the beginning... var repeat, extra, pad; if (abbrevPos === 0) { repeat = '0:'; extra = address === '::' ? '0' : ''; // for the address '::' pad = 9; // 7+2 (due to '::') // If the '::' is at the end... } else if (abbrevPos === (addressEnd - 1)) { repeat = ':0'; extra = ''; pad = 9; // 7+2 (due to '::') // If the '::' is in the middle... } else { repeat = ':0'; extra = ':'; pad = 8; // 6+2 (due to '::') } var replacement = repeat; pad -= address.split(':').length - 1; for (var i = 1; i < pad; i++) { replacement += repeat; } replacement += extra; address = address.replace('::', replacement); } // Remove leading zeros from each bloc as needed address = address.replace(/(^|:)0+([0-9A-Fa-f]{1,4})/g, '$1$2');  return address; };    /**  * **************** Morebits.quickForm ****************  * Morebits.quickForm is a class for creation of simple and standard forms without much  * specific coding.  *  * Index to Morebits.quickForm element types:  *  *   select    A combo box (aka drop-down).  *              - Attributes: name, label, multiple, size, list, event, disabled  *   option    An element for a combo box.  *              - Attributes: value, label, selected, disabled  *   optgroup  A group of "option"s.  *              - Attributes: label, list  *   field     A fieldset (aka group box).  *              - Attributes: name, label, disabled  *   checkbox  A checkbox. Must use "list" parameter.  *              - Attributes: name, list, event  *              - Attributes (within list): name, label, value, checked, disabled, event, subgroup  *   radio     A radio button. Must use "list" parameter.  *              - Attributes: name, list, event  *              - Attributes (within list): name, label, value, checked, disabled, event, subgroup  *   input     A text box.  *              - Attributes: name, label, value, size, disabled, required, readonly, maxlength, event  *   dyninput  A set of text boxes with "Remove" buttons and an "Add" button.  *              - Attributes: name, label, min, max, sublabel, value, size, maxlength, event  *   hidden    An invisible form field.  *              - Attributes: name, value  *   header    A level 5 header.  *              - Attributes: label  *   div       A generic placeholder element or label.  *              - Attributes: name, label  *   submit    A submit button. Morebits.simpleWindow moves these to the footer of the dialog.  *              - Attributes: name, label, disabled  *   button    A generic button.  *              - Attributes: name, label, disabled, event  *   textarea  A big, multi-line text box.  *              - Attributes: name, label, value, cols, rows, disabled, required, readonly  *   fragment  A DocumentFragment object.  *              - No attributes, and no global attributes except adminonly  *  * Global attributes: id, className, style, tooltip, extra, adminonly  */  /**  * @constructor  * @param {event} event - Function to execute when form is submitted  * @param {string} [eventType=submit] - Type of the event (default: submit)  */ Morebits.quickForm = function QuickForm(event, eventType) { this.root = new Morebits.quickForm.element({ type: 'form', event: event, eventType: eventType }); };  /**  * Renders the HTML output of the quickForm  * @returns {HTMLElement}  */ Morebits.quickForm.prototype.render = function QuickFormRender() { var ret = this.root.render(); ret.names = {}; return ret; };  /**  * Append element to the form  * @param {(Object|Morebits.quickForm.element)} data - a quickform element, or the object with which  * a quickform element is constructed.  * @returns {Morebits.quickForm.element} - same as what is passed to the function  */ Morebits.quickForm.prototype.append = function QuickFormAppend(data) { return this.root.append(data); };  /**  * @constructor  * @param {Object} data - Object representing the quickform element. See class documentation  * comment for available types and attributes for each.  */ Morebits.quickForm.element = function QuickFormElement(data) { this.data = data; this.childs = []; this.id = Morebits.quickForm.element.id++; };  Morebits.quickForm.element.id = 0;  /**  * Appends an element to current element  * @param {Morebits.quickForm.element} data  A quickForm element or the object required to  * create the quickForm element  * @returns {Morebits.quickForm.element} The same element passed in  */ Morebits.quickForm.element.prototype.append = function QuickFormElementAppend(data) { var child; if (data instanceof Morebits.quickForm.element) { child = data; } else { child = new Morebits.quickForm.element(data); } this.childs.push(child); return child; };  /**  * Renders the HTML output for the quickForm element  * This should be called without parameters: form.render()  * @returns {HTMLElement}  */ Morebits.quickForm.element.prototype.render = function QuickFormElementRender(internal_subgroup_id) { var currentNode = this.compute(this.data, internal_subgroup_id);  for (var i = 0; i < this.childs.length; ++i) { // do not pass internal_subgroup_id to recursive calls currentNode[1].appendChild(this.childs[i].render()); } return currentNode[0]; };  Morebits.quickForm.element.prototype.compute = function QuickFormElementCompute(data, in_id) { var node; var childContainder = null; var label; var id = (in_id ? in_id + '_' : '') + 'node_' + this.id; if (data.adminonly && !Morebits.userIsSysop) { // hell hack alpha data.type = 'hidden'; }  var i, current, subnode; switch (data.type) { case 'form': node = document.createElement('form'); node.className = 'quickform'; node.setAttribute('action', 'javascript:void(0);'); if (data.event) { node.addEventListener(data.eventType || 'submit', data.event, false); } break; case 'fragment': node = document.createDocumentFragment(); // fragments can't have any attributes, so just return it straight away return [ node, node ]; case 'select': node = document.createElement('div');  node.setAttribute('id', 'div_' + id); if (data.label) { label = node.appendChild(document.createElement('label')); label.setAttribute('for', id); label.appendChild(document.createTextNode(data.label)); } var select = node.appendChild(document.createElement('select')); if (data.event) { select.addEventListener('change', data.event, false); } if (data.multiple) { select.setAttribute('multiple', 'multiple'); } if (data.size) { select.setAttribute('size', data.size); } if (data.disabled) { select.setAttribute('disabled', 'disabled'); } select.setAttribute('name', data.name);  if (data.list) { for (i = 0; i < data.list.length; ++i) {  current = data.list[i];  if (current.list) { current.type = 'optgroup'; } else { current.type = 'option'; }  subnode = this.compute(current); select.appendChild(subnode[0]); } } childContainder = select; break; case 'option': node = document.createElement('option'); node.values = data.value; node.setAttribute('value', data.value); if (data.selected) { node.setAttribute('selected', 'selected'); } if (data.disabled) { node.setAttribute('disabled', 'disabled'); } node.setAttribute('label', data.label); node.appendChild(document.createTextNode(data.label)); break; case 'optgroup': node = document.createElement('optgroup'); node.setAttribute('label', data.label);  if (data.list) { for (i = 0; i < data.list.length; ++i) {  current = data.list[i]; current.type = 'option'; // must be options here  subnode = this.compute(current); node.appendChild(subnode[0]); } } break; case 'field': node = document.createElement('fieldset'); label = node.appendChild(document.createElement('legend')); label.appendChild(document.createTextNode(data.label)); if (data.name) { node.setAttribute('name', data.name); } if (data.disabled) { node.setAttribute('disabled', 'disabled'); } break; case 'checkbox': case 'radio': node = document.createElement('div'); if (data.list) { for (i = 0; i < data.list.length; ++i) { var cur_id = id + '_' + i; current = data.list[i]; var cur_div; if (current.type === 'header') { // inline hack cur_div = node.appendChild(document.createElement('h6')); cur_div.appendChild(document.createTextNode(current.label)); if (current.tooltip) { Morebits.quickForm.element.generateTooltip(cur_div, current); } continue; } cur_div = node.appendChild(document.createElement('div')); subnode = cur_div.appendChild(document.createElement('input')); subnode.values = current.value; subnode.setAttribute('value', current.value); subnode.setAttribute('type', data.type); subnode.setAttribute('id', cur_id); subnode.setAttribute('name', current.name || data.name);  // If name is provided on the individual checkbox, add a data-single // attribute which indicates it isn't part of a list of checkboxes with // same name. Used in getInputData() if (current.name) { subnode.setAttribute('data-single', 'data-single'); }  if (current.checked) { subnode.setAttribute('checked', 'checked'); } if (current.disabled) { subnode.setAttribute('disabled', 'disabled'); } label = cur_div.appendChild(document.createElement('label')); label.appendChild(document.createTextNode(current.label)); label.setAttribute('for', cur_id); if (current.tooltip) { Morebits.quickForm.element.generateTooltip(label, current); } // styles go on the label, doesn't make sense to style a checkbox/radio if (current.style) { label.setAttribute('style', current.style); }  var event; if (current.subgroup) { var tmpgroup = current.subgroup;  if (!Array.isArray(tmpgroup)) { tmpgroup = [ tmpgroup ]; }  var subgroupRaw = new Morebits.quickForm.element({ type: 'div', id: id + '_' + i + '_subgroup' }); $.each(tmpgroup, function(idx, el) { var newEl = $.extend({}, el); if (!newEl.type) { newEl.type = data.type; } newEl.name = (current.name || data.name) + '.' + newEl.name; subgroupRaw.append(newEl); });  var subgroup = subgroupRaw.render(cur_id); subgroup.className = 'quickformSubgroup'; subnode.subgroup = subgroup; subnode.shown = false;  event = function(e) { if (e.target.checked) { e.target.parentNode.appendChild(e.target.subgroup); if (e.target.type === 'radio') { var name = e.target.name; if (e.target.form.names[name] !== undefined) { e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup); } e.target.form.names[name] = e.target; } } else { e.target.parentNode.removeChild(e.target.subgroup); } }; subnode.addEventListener('change', event, true); if (current.checked) { subnode.parentNode.appendChild(subgroup); } } else if (data.type === 'radio') { event = function(e) { if (e.target.checked) { var name = e.target.name; if (e.target.form.names[name] !== undefined) { e.target.form.names[name].parentNode.removeChild(e.target.form.names[name].subgroup); } delete e.target.form.names[name]; } }; subnode.addEventListener('change', event, true); } // add users' event last, so it can interact with the subgroup if (data.event) { subnode.addEventListener('change', data.event, false); } else if (current.event) { subnode.addEventListener('change', current.event, true); } } } if (data.shiftClickSupport && data.type === 'checkbox') { Morebits.checkboxShiftClickSupport(Morebits.quickForm.getElements(node, data.name)); } break; case 'input': node = document.createElement('div'); node.setAttribute('id', 'div_' + id);  if (data.label) { label = node.appendChild(document.createElement('label')); label.appendChild(document.createTextNode(data.label)); label.setAttribute('for', data.id || id); }  subnode = node.appendChild(document.createElement('input')); if (data.value) { subnode.setAttribute('value', data.value); } subnode.setAttribute('name', data.name); subnode.setAttribute('type', 'text'); if (data.size) { subnode.setAttribute('size', data.size); } if (data.disabled) { subnode.setAttribute('disabled', 'disabled'); } if (data.required) { subnode.setAttribute('required', 'required'); } if (data.readonly) { subnode.setAttribute('readonly', 'readonly'); } if (data.maxlength) { subnode.setAttribute('maxlength', data.maxlength); } if (data.event) { subnode.addEventListener('keyup', data.event, false); } childContainder = subnode; break; case 'dyninput': var min = data.min || 1; var max = data.max || Infinity;  node = document.createElement('div');  label = node.appendChild(document.createElement('h5')); label.appendChild(document.createTextNode(data.label));  var listNode = node.appendChild(document.createElement('div'));  var more = this.compute({ type: 'button', label: 'more', disabled: min >= max, event: function(e) { var new_node = new Morebits.quickForm.element(e.target.sublist); e.target.area.appendChild(new_node.render());  if (++e.target.counter >= e.target.max) { e.target.setAttribute('disabled', 'disabled'); } e.stopPropagation(); } });  node.appendChild(more[0]); var moreButton = more[1];  var sublist = { type: '_dyninput_element', label: data.sublabel || data.label, name: data.name, value: data.value, size: data.size, remove: false, maxlength: data.maxlength, event: data.event };  for (i = 0; i < min; ++i) { var elem = new Morebits.quickForm.element(sublist); listNode.appendChild(elem.render()); } sublist.remove = true; sublist.morebutton = moreButton; sublist.listnode = listNode;  moreButton.sublist = sublist; moreButton.area = listNode; moreButton.max = max - min; moreButton.counter = 0; break; case '_dyninput_element': // Private, similar to normal input node = document.createElement('div');  if (data.label) { label = node.appendChild(document.createElement('label')); label.appendChild(document.createTextNode(data.label)); label.setAttribute('for', id); }  subnode = node.appendChild(document.createElement('input')); if (data.value) { subnode.setAttribute('value', data.value); } subnode.setAttribute('name', data.name); subnode.setAttribute('type', 'text'); if (data.size) { subnode.setAttribute('size', data.size); } if (data.maxlength) { subnode.setAttribute('maxlength', data.maxlength); } if (data.event) { subnode.addEventListener('keyup', data.event, false); } if (data.remove) { var remove = this.compute({ type: 'button', label: 'remove', event: function(e) { var list = e.target.listnode; var node = e.target.inputnode; var more = e.target.morebutton;  list.removeChild(node); --more.counter; more.removeAttribute('disabled'); e.stopPropagation(); } }); node.appendChild(remove[0]); var removeButton = remove[1]; removeButton.inputnode = node; removeButton.listnode = data.listnode; removeButton.morebutton = data.morebutton; } break; case 'hidden': node = document.createElement('input'); node.setAttribute('type', 'hidden'); node.values = data.value; node.setAttribute('value', data.value); node.setAttribute('name', data.name); break; case 'header': node = document.createElement('h5'); node.appendChild(document.createTextNode(data.label)); break; case 'div': node = document.createElement('div'); if (data.name) { node.setAttribute('name', data.name); } if (data.label) { if (!Array.isArray(data.label)) { data.label = [ data.label ]; } var result = document.createElement('span'); result.className = 'quickformDescription'; for (i = 0; i < data.label.length; ++i) { if (typeof data.label[i] === 'string') { result.appendChild(document.createTextNode(data.label[i])); } else if (data.label[i] instanceof Element) { result.appendChild(data.label[i]); } } node.appendChild(result); } break; case 'submit': node = document.createElement('span'); childContainder = node.appendChild(document.createElement('input')); childContainder.setAttribute('type', 'submit'); if (data.label) { childContainder.setAttribute('value', data.label); } childContainder.setAttribute('name', data.name || 'submit'); if (data.disabled) { childContainder.setAttribute('disabled', 'disabled'); } break; case 'button': node = document.createElement('span'); childContainder = node.appendChild(document.createElement('input')); childContainder.setAttribute('type', 'button'); if (data.label) { childContainder.setAttribute('value', data.label); } childContainder.setAttribute('name', data.name); if (data.disabled) { childContainder.setAttribute('disabled', 'disabled'); } if (data.event) { childContainder.addEventListener('click', data.event, false); } break; case 'textarea': node = document.createElement('div'); node.setAttribute('id', 'div_' + id); if (data.label) { label = node.appendChild(document.createElement('h5')); var labelElement = document.createElement('label'); labelElement.textContent = data.label; labelElement.setAttribute('for', data.id || id); label.appendChild(labelElement); } subnode = node.appendChild(document.createElement('textarea')); subnode.setAttribute('name', data.name); if (data.cols) { subnode.setAttribute('cols', data.cols); } if (data.rows) { subnode.setAttribute('rows', data.rows); } if (data.disabled) { subnode.setAttribute('disabled', 'disabled'); } if (data.required) { subnode.setAttribute('required', 'required'); } if (data.readonly) { subnode.setAttribute('readonly', 'readonly'); } if (data.value) { subnode.value = data.value; } childContainder = subnode; break; default: throw new Error('Morebits.quickForm: unknown element type ' + data.type.toString()); }  if (!childContainder) { childContainder = node; } if (data.tooltip) { Morebits.quickForm.element.generateTooltip(label || node, data); }  if (data.extra) { childContainder.extra = data.extra; } if (data.style) { childContainder.setAttribute('style', data.style); } if (data.className) { childContainder.className = childContainder.className ? childContainder.className + ' ' + data.className : data.className; } childContainder.setAttribute('id', data.id || id);  return [ node, childContainder ]; };  /**  * Create a jquery.ui-based tooltip.  * @requires jquery.ui  * @param {HTMLElement} node - the HTML element beside which a tooltip is to be generated  * @param {Object} data - tooltip-related configuration data  */ Morebits.quickForm.element.generateTooltip = function QuickFormElementGenerateTooltip(node, data) { var tooltipButton = node.appendChild(document.createElement('span')); tooltipButton.className = 'morebits-tooltipButton'; tooltipButton.title = data.tooltip; // Provides the content for jQuery UI tooltipButton.appendChild(document.createTextNode('?')); $(tooltipButton).tooltip({ position: { my: 'left top', at: 'center bottom', collision: 'flipfit' }, // Deprecated in UI 1.12, but MW stuck on 1.9.2 indefinitely; see #398 and T71386 tooltipClass: 'morebits-ui-tooltip' }); };   // Some utility methods for manipulating quickForms after their creation: // (None of these work for "dyninput" type fields at present)  /**  * Returns an object containing all filled form data entered by the user, with the object  * keys being the form element names. Disabled fields will be ignored, but not hidden fields.  * @param {HTMLFormElement} form  * @returns {Object} with field names as keys, input data as values  */ Morebits.quickForm.getInputData = function(form) { var result = {};  for (var i = 0; i < form.elements.length; i++) { var field = form.elements[i]; if (field.disabled || !field.name || !field.type || field.type === 'submit' || field.type === 'button') { continue; }  // For elements in subgroups, quickform prepends element names with // name of the parent group followed by a period, get rid of that. var fieldNameNorm = field.name.slice(field.name.indexOf('.') + 1);  switch (field.type) { case 'radio': if (field.checked) { result[fieldNameNorm] = field.value; } break; case 'checkbox': if (field.dataset.single) { result[fieldNameNorm] = field.checked; // boolean } else { result[fieldNameNorm] = result[fieldNameNorm] || []; if (field.checked) { result[fieldNameNorm].push(field.value); } } break; case 'select-multiple': result[fieldNameNorm] = $(field).val(); // field.value doesn't work break; case 'text': // falls through case 'textarea': result[fieldNameNorm] = field.value.trim(); break; default: // could be select-one, date, number, email, etc if (field.value) { result[fieldNameNorm] = field.value; } break; } } return result; };   /**  * Returns all form elements with a given field name or ID  * @param {HTMLFormElement} form  * @param {string} fieldName - the name or id of the fields  * @returns {HTMLElement[]} - array of matching form elements  */ Morebits.quickForm.getElements = function QuickFormGetElements(form, fieldName) { var $form = $(form); fieldName = $.escapeSelector(fieldName); // sanitize input var $elements = $form.find('[name="' + fieldName + '"]'); if ($elements.length > 0) { return $elements.toArray(); } $elements = $form.find('#' + fieldName); return $elements.toArray(); };  /**  * Searches the array of elements for a checkbox or radio button with a certain  * `value` attribute, and returns the first such element. Returns null if not found.  * @param {HTMLInputElement[]} elementArray - array of checkbox or radio elements  * @param {string} value - value to search for  * @returns {HTMLInputElement}  */ Morebits.quickForm.getCheckboxOrRadio = function QuickFormGetCheckboxOrRadio(elementArray, value) { var found = $.grep(elementArray, function(el) { return el.value === value; }); if (found.length > 0) { return found[0]; } return null; };  /**  * Returns the 
containing the form element, or the form element itself * May not work as expected on checkboxes or radios * @param {HTMLElement} element * @returns {HTMLElement} */ Morebits.quickForm.getElementContainer = function QuickFormGetElementContainer(element) { // for divs, headings and fieldsets, the container is the element itself if (element instanceof HTMLFieldSetElement || element instanceof HTMLDivElement || element instanceof HTMLHeadingElement) { return element; } // for others, just return the parent node return element.parentNode; }; /** * Gets the HTML element that contains the label of the given form element * (mainly for internal use) * @param {(HTMLElement|Morebits.quickForm.element)} element * @returns {HTMLElement} */ Morebits.quickForm.getElementLabelObject = function QuickFormGetElementLabelObject(element) { // for buttons, divs and headers, the label is on the element itself if (element.type === 'button' || element.type === 'submit' || element instanceof HTMLDivElement || element instanceof HTMLHeadingElement) { return element; // for fieldsets, the label is the child element } else if (element instanceof HTMLFieldSetElement) { return element.getElementsByTagName('legend')[0]; // for textareas, the label is the sibling
element } else if (element instanceof HTMLTextAreaElement) { return element.parentNode.getElementsByTagName('h5')[0]; } // for others, the label is the sibling

Tags:

🔥 Trending searches on Wiki Tiếng Việt:

Lịch sử Sài Gòn – Thành phố Hồ Chí MinhĐạo giáoCông (vật lý học)Tranh Đông HồNew ZealandNgân hàng Thương mại cổ phần Đầu tư và Phát triển Việt NamPhùng Hữu PhúRonaldo (cầu thủ bóng đá Brasil)Phong trào Cần VươngCôn ĐảoKhối lượng riêngChiến dịch Linebacker IINguyễn DuTư tưởng Hồ Chí MinhLiên minh châu ÂuNhật BảnBắc NinhThiên địa (trang web)Vụ án Lê Văn LuyệnĐài Truyền hình Kỹ thuật số VTCInter MilanNguyễn Thị Kim NgânNhà giả kim (tiểu thuyết)YMê KôngChung kết giải vô địch bóng đá U-23 châu Á 2018Tottenham Hotspur F.C.Đại tướng Quân đội nhân dân Việt NamBộ Tư lệnh Cảnh sát Cơ động (Việt Nam)Quân khu 5, Quân đội nhân dân Việt NamNgày Quốc tế Lao độngNVõ Thị Ánh XuânVăn hóaNepalPhápTam ThểDòng điệnRừng mưa nhiệt đớiNhà ĐườngĐiện BiênĐền HùngMắt biếc (tiểu thuyết)Châu MỹInternetTrường ChinhNewJeansDanh sách quốc gia theo GDP (danh nghĩa) bình quân đầu ngườiNgân hàng Thương mại Cổ phần Công thương Việt NamTổng cục Tình báo, Bộ Công an (Việt Nam)Suni Hạ LinhPhật giáoMinh Lan TruyệnCarlo AncelottiĐà LạtNguyệt thựcChữ NômDanh sách Anh hùng Lực lượng vũ trang nhân dânChiến tranh biên giới Việt Nam – CampuchiaAngolaCông an thành phố Hải PhòngDubaiTrần Quốc TỏLiverpool F.C.Liên XôHoàng Thị Thúy LanThích Nhất HạnhNgô QuyềnBiển ĐôngDanh sách thủy điện tại Việt NamQuân khu 9, Quân đội nhân dân Việt NamDoraemonTiếng ViệtGiải vô địch bóng đá trong nhà thế giới 2024Nguyễn Trung TrựcChiến tranh Nguyên Mông – Đại Việt lần 2Tôn Đức ThắngGiải bóng đá Ngoại hạng Anh🡆 More