| Modul ini dilindungi. Modul ini sangat mencolok yang digunakan oleh banyak halaman, atau sangat sering disubstitusikan. Karena vandalisme atau kesalahan akan mempengaruhi banyak halaman, dan suntingan kecil dapat memberi beban besar pada server, modul ini dilindungi dari penyuntingan. |
| Modul Lua ini digunakan pada banyak halaman dan perubahannya kemungkinan memicu perubahan massal pada semua halaman yang menggunakannya. Uji cobalah di subhalaman /bak pasir atau /kasus uji Modul:Article history, atau bak pasir modul Anda. Pertimbangkan untuk mendiskusikan perubahan di halaman pembicaraan sebelum mengimplementasikannya. |
| Modul ini menggunakan Lua: |
| Modul ini menggunakan TemplateStyles: |
Modul ini mengimplementasikan Templat:Article history (sunting | bicara | riwayat | pranala | pantau | log). Silakan lihat halaman templat untuk dokumentasi mengenai cara menggunakannya.
Detail teknisDetail teknis
Modul mempunyai sebuah konfigurasi di Modul:Article history/config, yang dapat digunakan untuk menerjemahkan/memodifikasi templat untuk penggunaan di wiki lainnya. Lihat modul konfigurasi untuk petunjuk mengenai cara memodifikasinya. Konfigurasi untuk Wikipedia bahasa Inggris sangat rumit, namun modul ini juga dapat digunakan dengan cara yang jauh lebih sederhana. Jika Anda ingin menggunakan fitur-fitur lebih lanjut yang disediakan oleh modul ini, anda dapat merujuk pada dokumentasi kelas di bawah ini.
Terdapat juga kelas Kategori di Modul:Article history/Category, yang dapat digunakan di modul konfigurasi dan utama.
Penyunting dapat melakukan uji coba pada halaman bak pasir (buat | cermin) dan kasus uji (buat) modul ini.
Subhalaman modul ini.
------------------------------------------------------------------------------- -- Article history -- -- This module allows editors to link to all the significant events in an -- article's history, such as good article nominations and featured article -- nominations. It also displays its current status, as well as other -- information, such as the date it was featured on the main page. ------------------------------------------------------------------------------- localCONFIG_PAGE='Modul:Article history/config' localWRAPPER_TEMPLATE='Templat:Article history' localDEBUG_MODE=false-- If true, errors are not caught. -- Load required modules. require('strict') localCategory=require('Modul:Article history/Category') localyesno=require('Modul:Yesno') locallang=mw.language.getContentLanguage() ------------------------------------------------------------------------------- -- Helper functions ------------------------------------------------------------------------------- localfunctionisPositiveInteger(num) returntype(num)=='number' andmath.floor(num)==num andnum>0 andnum<math.huge end localfunctionsubstituteParams(msg,...) returnmw.message.newRawMessage(msg,...):plain() end localfunctionmakeUrlLink(url,display) returnstring.format('[%s %s]',url,display) end localfunctionmaybeCallFunc(val,...) -- Checks whether val is a function, and if so calls it with the specified -- arguments. Otherwise val is returned as-is. iftype(val)=='function'then returnval(...) else returnval end end localfunctionrenderImage(image,caption,size) ifcaptionthen caption='|'..caption else caption='' end returnstring.format('[[Berkas:%s|%s%s]]',image,size,caption) end localfunctionaddMixin(class,mixin) -- Add a mixin to a class. The functions will be shared across classes, so -- don't use it for functions that keep state. forname,methodinpairs(mixin)do class[name]=method end end ------------------------------------------------------------------------------- -- Message mixin -- This mixin is used by all classes to add message-related methods. ------------------------------------------------------------------------------- localMessage={} functionMessage:message(key,...) -- This fetches the message from the config with the specified key, and -- substitutes parameters $1, $2 etc. with the subsequent values it is -- passed. localmsg=self.cfg.msg[key] ifselect('#',...)>0then returnsubstituteParams(msg,...) else returnmsg end end functionMessage:raiseError(msg,help) -- Raises an error with the specified message and help link. Execution -- stops unless the error is caught. This is used for errors where -- subsequent processing becomes impossible. localerrorText ifhelpthen errorText=self:message('error-message-help',msg,help) else errorText=self:message('error-message-nohelp',msg) end error(errorText,0) end functionMessage:addWarning(msg,help) -- Adds a warning to the object's warnings table. Execution continues as -- normal. This is used for errors that should be fixed but that do not -- prevent the module from outputting something useful. self.warnings=self.warningsor{} localwarningText ifhelpthen warningText=self:message('warning-help',msg,help) else warningText=self:message('warning-nohelp',msg) end table.insert(self.warnings,warningText) end functionMessage:getWarnings() returnself.warningsor{} end ------------------------------------------------------------------------------- -- Row class -- This class represents one row in the template. ------------------------------------------------------------------------------- localRow={} Row.__index=Row addMixin(Row,Message) functionRow.new(data) localobj=setmetatable({},Row) obj.cfg=data.cfg obj.currentTitle=data.currentTitle obj.makeData=data.makeData-- used by Row:getData returnobj end functionRow:_cachedTry(cacheKey,errorCacheKey,func) -- This method is for use in Row object methods that are called more than -- once. The results of such methods should be cached to avoid unnecessary -- processing. We also cache any errors found and abort if an error was -- raised previously, otherwise error messages could be displayed multiple -- times. -- -- We use false as a key to cache nil results, so func cannot return false. -- -- @param cacheKey The key to cache successful results with -- @param errorCacheKey The key to cache errors with -- @param func an anonymous function that returns the method result ifself[errorCacheKey]then returnnil end localret=self[cacheKey] ifretthen returnret elseifret==falsethen returnnil end localsuccess ifDEBUG_MODEthen success=true ret=func() else success,ret=pcall(func) end ifsuccessthen ifretthen self[cacheKey]=ret returnret else self[cacheKey]=false returnnil end else self[errorCacheKey]=true -- We have already formatted the error message, so no need to format it -- again. error(ret,0) end end functionRow:getData(articleHistoryObj) returnself:_cachedTry('_dataCache','_isDataError',function() returnself.makeData(articleHistoryObj) end) end functionRow:setIconValues(icon,caption,size) self.icon=icon self.iconCaption=caption self.iconSize=size end functionRow:getIcon(articleHistoryObj) returnmaybeCallFunc(self.icon,articleHistoryObj,self) end functionRow:getIconCaption(articleHistoryObj) returnmaybeCallFunc(self.iconCaption,articleHistoryObj,self) end functionRow:getIconSize() returnself.iconSizeorself.cfg.defaultIconSizeor'30px' end functionRow:renderIcon(articleHistoryObj) localicon=self:getIcon(articleHistoryObj) ifnoticonthen returnnil end returnrenderImage( icon, self:getIconCaption(articleHistoryObj), self:getIconSize() ) end functionRow:setNoticeBarIconValues(icon,caption,size) self.noticeBarIcon=icon self.noticeBarIconCaption=caption self.noticeBarIconSize=size end functionRow:getNoticeBarIcon(articleHistoryObj) localicon=maybeCallFunc(self.noticeBarIcon,articleHistoryObj,self) ificon==truethen icon=self:getIcon(articleHistoryObj) ifnoticonthen self:raiseError( self:message('row-error-missing-icon'), self:message('row-error-missing-icon-help') ) end end returnicon end functionRow:getNoticeBarIconCaption(articleHistoryObj) localcaption=maybeCallFunc( self.noticeBarIconCaption, articleHistoryObj, self ) ifnotcaptionthen caption=self:getIconCaption(articleHistoryObj) end returncaption end functionRow:getNoticeBarIconSize() returnself.noticeBarIconSizeorself.cfg.defaultNoticeBarIconSizeor'15px' end functionRow:exportNoticeBarIcon(articleHistoryObj) localicon=self:getNoticeBarIcon(articleHistoryObj) ifnoticonthen returnnil end returnrenderImage( icon, self:getNoticeBarIconCaption(articleHistoryObj), self:getNoticeBarIconSize() ) end functionRow:setText(text) self.text=text end functionRow:getText(articleHistoryObj) returnmaybeCallFunc(self.text,articleHistoryObj,self) end functionRow:exportHtml(articleHistoryObj) ifself._htmlthen returnself._html end localtext=self:getText(articleHistoryObj) ifnottextthen returnnil end localhtml=mw.html.create('tr') html :tag('td') :addClass('mbox-image') :wikitext(self:renderIcon(articleHistoryObj)) :done() :tag('td') :addClass('mbox-text') :wikitext(text) self._html=html returnhtml end functionRow:setCategories(val) -- Set the categories from the object's config. val can be either an array -- of strings or a function returning an array of category objects. self.categories=val end functionRow:getCategories(articleHistoryObj) localret={} iftype(self.categories)=='table'then for_,catinipairs(self.categories)do ret[#ret+1]=Category.new(cat) end elseiftype(self.categories)=='function'then localt=self.categories(articleHistoryObj,self)or{} for_,categoryObjinipairs(t)do ret[#ret+1]=categoryObj end end returnret end ------------------------------------------------------------------------------- -- Status class -- Status objects deal with possible current statuses of the article. ------------------------------------------------------------------------------- localStatus=setmetatable({},Row) Status.__index=Status functionStatus.new(data) localobj=Row.new(data) setmetatable(obj,Status) obj.id=data.id obj.statusCfg=obj.cfg.statuses[obj.id] obj.name=obj.statusCfg.name obj:setIconValues( obj.statusCfg.icon, obj.statusCfg.iconCaptionorobj.name, data.iconSize ) obj:setNoticeBarIconValues( obj.statusCfg.noticeBarIcon, obj.statusCfg.noticeBarIconCaptionorobj.name, obj.statusCfg.noticeBarIconSize ) obj:setText(obj.statusCfg.text) obj:setCategories(obj.statusCfg.categories) returnobj end functionStatus:getIconSize() returnself.iconSize orself.statusCfg.iconSize orself.cfg.defaultStatusIconSize or'50px' end functionStatus:getText(articleHistoryObj) localtext=Row.getText(self,articleHistoryObj) iftextthen returnsubstituteParams( text, self.currentTitle.subjectPageTitle.prefixedText, self.currentTitle.text ) end end ------------------------------------------------------------------------------- -- MultiStatus class -- For when an article can have multiple distinct statuses, e.g. former -- featured article status and good article status. ------------------------------------------------------------------------------- localMultiStatus=setmetatable({},Row) MultiStatus.__index=MultiStatus functionMultiStatus.new(data) localobj=Row.new(data) setmetatable(obj,MultiStatus) obj.id=data.id obj.statusCfg=obj.cfg.statuses[data.id] obj.name=obj.statusCfg.name -- Set child status objects localfunctiongetChildStatusData(data,id,iconSize) localret={} fork,vinpairs(data)do ret[k]=v end ret.id=id ret.iconSize=iconSize returnret end obj.statuses={} localdefaultIconSize=obj.cfg.defaultMultiStatusIconSizeor'30px' for_,idinipairs(obj.statusCfg.statuses)do table.insert(obj.statuses,Status.new(getChildStatusData( data, id, obj.cfg.statuses[id].iconMultiSizeordefaultIconSize ))) end returnobj end functionMultiStatus:exportHtml(articleHistoryObj) localret=mw.html.create() for_,objinipairs(self.statuses)do ret:node(obj:exportHtml(articleHistoryObj)) end returnret end functionMultiStatus:getCategories(articleHistoryObj) localret={} for_,objinipairs(self.statuses)do for_,categoryObjinipairs(obj:getCategories(articleHistoryObj))do ret[#ret+1]=categoryObj end end returnret end functionMultiStatus:exportNoticeBarIcon() localret={} for_,objinipairs(self.statuses)do ret[#ret+1]=obj:exportNoticeBarIcon() end returntable.concat(ret) end functionMultiStatus:getWarnings() localret={} for_,objinipairs(self.statuses)do for_,msginipairs(obj:getWarnings())do ret[#ret+1]=msg end end returnret end ------------------------------------------------------------------------------- -- Notice class -- Notice objects contain notices about an article that aren't part of its -- current status, e.g. the date an article was featured on the main page. ------------------------------------------------------------------------------- localNotice=setmetatable({},Row) Notice.__index=Notice functionNotice.new(data) localobj=Row.new(data) setmetatable(obj,Notice) obj:setIconValues( data.icon, data.iconCaption, data.iconSize ) obj:setNoticeBarIconValues( data.noticeBarIcon, data.noticeBarIconCaption, data.noticeBarIconSize ) obj:setText(data.text) obj:setCategories(data.categories) returnobj end ------------------------------------------------------------------------------- -- Action class -- Action objects deal with a single action in the history of the article. We -- use getter methods rather than properties for the name and result, etc., as -- their processing needs to be delayed until after the status object has been -- initialised. The status object needs to parse the action objects when it is -- initialised, and the value of some names, etc., in the action objects depend -- on the status object, so this is necessary to avoid errors/infinite loops. ------------------------------------------------------------------------------- localAction=setmetatable({},Row) Action.__index=Action functionAction.new(data) localobj=Row.new(data) setmetatable(obj,Action) obj.paramNum=data.paramNum -- Set the ID do ifnotdata.codethen obj:raiseError( obj:message('action-error-no-code',obj:getParameter('code')), obj:message('action-error-no-code-help') ) end localcode=mw.ustring.upper(data.code) obj.id=obj.cfg.actions[code]andobj.cfg.actions[code].id ifnotobj.idthen obj:raiseError( obj:message( 'action-error-invalid-code', data.code, obj:getParameter('code') ), obj:message('action-error-invalid-code-help') ) end end -- Add a shortcut for this action's config. obj.actionCfg=obj.cfg.actions[obj.id] -- Set the link obj.link=data.linkorobj.currentTitle.talkPageTitle.prefixedText -- Set the result ID do localresultCode=data.resultCode andmw.ustring.lower(data.resultCode) or'_BLANK' ifobj.actionCfg.results[resultCode]then obj.resultId=obj.actionCfg.results[resultCode].id elseifresultCode=='_BLANK'then obj:raiseError( obj:message( 'action-error-blank-result', obj.id, obj:getParameter('resultCode') ), obj:message('action-error-blank-result-help') ) else obj:raiseError( obj:message( 'action-error-invalid-result', data.resultCode, obj.id, obj:getParameter('resultCode') ), obj:message('action-error-invalid-result-help') ) end end -- Set the date ifdata.datethen localsuccess,date=pcall( lang.formatDate, lang, obj:message('action-date-format'), data.date ) ifsuccessanddatethen obj.date=date else obj:addWarning( obj:message( 'action-warning-invalid-date', data.date, obj:getParameter('date') ), obj:message('action-warning-invalid-date-help') ) end else obj:addWarning( obj:message( 'action-warning-no-date', obj.paramNum, obj:getParameter('date'), obj:getParameter('code') ), obj:message('action-warning-no-date-help') ) end obj.date=obj.dateorobj:message('action-date-missing') -- Set the oldid obj.oldid=tonumber(data.oldid) ifdata.oldidand(notobj.oldidornotisPositiveInteger(obj.oldid))then obj.oldid=nil obj:addWarning( obj:message( 'action-warning-invalid-oldid', data.oldid, obj:getParameter('oldid') ), obj:message('action-warning-invalid-oldid-help') ) end -- Set the notice bar icon values obj:setNoticeBarIconValues( data.noticeBarIcon, data.noticeBarIconCaption, data.noticeBarIconSize ) -- Set the categories obj:setCategories(obj.actionCfg.categories) returnobj end functionAction:getParameter(key) -- Finds the original parameter name for the given key that was passed to -- Action.new. localprefix=self.cfg.actionParamPrefix localsuffix fork,vinpairs(self.cfg.actionParamSuffixes)do ifv==keythen suffix=k break end end ifnotsuffixthen error('invalid key "'..tostring(key)..'" passed to Action:getParameter',2) end returnprefix..tostring(self.paramNum)..suffix end functionAction:getName(articleHistoryObj) returnmaybeCallFunc(self.actionCfg.name,articleHistoryObj,self) end functionAction:getResult(articleHistoryObj) returnmaybeCallFunc( self.actionCfg.results[self.resultId].text, articleHistoryObj, self ) end functionAction:exportHtml(articleHistoryObj) ifself._htmlthen returnself._html end localrow=mw.html.create('tr') -- Date cell localdateCell=row:tag('td') ifself.oldidthen dateCell :tag('span') :addClass('plainlinks') :wikitext(makeUrlLink( self.currentTitle.subjectPageTitle:fullUrl{oldid=self.oldid}, self.date )) else dateCell:wikitext(self.date) end -- Process cell row :tag('td') :wikitext(string.format( "'''[[%s|%s]]'''", self.link, self:getName(articleHistoryObj) )) -- Result cell row :tag('td') :wikitext(self:getResult(articleHistoryObj)) self._html=row returnrow end ------------------------------------------------------------------------------- -- CollapsibleNotice class -- This class makes notices that go in the collapsible part of the template, -- underneath the list of actions. ------------------------------------------------------------------------------- localCollapsibleNotice=setmetatable({},Row) CollapsibleNotice.__index=CollapsibleNotice functionCollapsibleNotice.new(data) localobj=Row.new(data) setmetatable(obj,CollapsibleNotice) obj:setIconValues( data.icon, data.iconCaption, data.iconSize ) obj:setNoticeBarIconValues( data.noticeBarIcon, data.noticeBarIconCaption, data.noticeBarIconSize ) obj:setText(data.text) obj:setCollapsibleText(data.collapsibleText) obj:setCategories(data.categories) returnobj end functionCollapsibleNotice:setCollapsibleText(s) self.collapsibleText=s end functionCollapsibleNotice:getCollapsibleText(articleHistoryObj) returnmaybeCallFunc(self.collapsibleText,articleHistoryObj,self) end functionCollapsibleNotice:getIconSize() returnself.iconSize orself.cfg.defaultCollapsibleNoticeIconSize or'20px' end functionCollapsibleNotice:exportHtml(articleHistoryObj,isInCollapsibleTable) localcacheKey=isInCollapsibleTable and'_htmlCacheCollapsible' or'_htmlCacheDefault' returnself:_cachedTry(cacheKey,'_isHtmlError',function() localtext=self:getText(articleHistoryObj) ifnottextthen returnnil end localfunctionmaybeMakeCollapsibleTable(cell,text,collapsibleText) -- If collapsible text is specified, makes a collapsible table -- inside the cell with two rows, a header row with one cell and a -- collapsed row with one cell. These are filled with text and -- collapsedText, respectively. If no collapsible text is -- specified, the text is added to the cell as-is. ifcollapsibleTextthen cell :tag('div') :addClass('mw-collapsible mw-collapsed') :tag('div') :wikitext(text) :done() :tag('div') :addClass('mw-collapsible-content') :css('border','1px silver solid') :wikitext(collapsibleText) else cell:wikitext(text) end end localhtml=mw.html.create('tr') localicon=self:renderIcon(articleHistoryObj) localcollapsibleText=self:getCollapsibleText(articleHistoryObj) ifisInCollapsibleTablethen localtextCell=html:tag('td') :attr('colspan',3) :css('width','100%') localrowText ificonthen rowText=icon..' '..text else rowText=text end maybeMakeCollapsibleTable(textCell,rowText,collapsibleText) else localtextCell=html :tag('td') :addClass('mbox-image') :wikitext(icon) :done() :tag('td') :addClass('mbox-text') maybeMakeCollapsibleTable(textCell,text,collapsibleText) end returnhtml end) end ------------------------------------------------------------------------------- -- ArticleHistory class -- This class represents the whole template. ------------------------------------------------------------------------------- localArticleHistory={} ArticleHistory.__index=ArticleHistory addMixin(ArticleHistory,Message) functionArticleHistory.new(args,cfg,currentTitle) localobj=setmetatable({},ArticleHistory) -- Set input obj.args=argsor{} obj.currentTitle=currentTitleormw.title.getCurrentTitle() -- Define object structure. obj._errors={} obj._allObjectsCache={} -- Format the config localfunctionsubstituteAliases(t,ret) -- This function substitutes strings found in an "aliases" subtable -- as keys in the parent table. It works recursively, so "aliases" -- subtables can be placed at any level. It assumes that tables will -- not be nested recursively, which should be true in the case of our -- config file. ret=retor{} fork,vinpairs(t)do ifk~='aliases'then iftype(v)=='table'then localnewRet={} ret[k]=newRet ifv.aliasesthen for_,aliasinipairs(v.aliases)do ret[alias]=newRet end end substituteAliases(v,newRet) else ret[k]=v end end end returnret end obj.cfg=substituteAliases(cfgorrequire(CONFIG_PAGE)) --[[ -- Get a table of the arguments sorted by prefix and number. Non-string -- keys and keys that don't contain a number are ignored. (This means that -- positional parameters are ignored, as they are numbers, not strings.) -- The parameter numbers are stored in the first positional parameter of -- the subtables, and any gaps are removed so that the tables can be -- iterated over with ipairs. -- -- For example, these arguments: -- {a1x = 'eggs', a1y = 'spam', a2x = 'chips', b1z = 'beans', b3x = 'bacon'} -- would translate into this prefixArgs table. -- { -- a = { -- {1, x = 'eggs', y = 'spam'}, -- {2, x = 'chips'} -- }, -- b = { -- {1, z = 'beans'}, -- {3, x = 'bacon'} -- } -- } --]] do localprefixArgs={} fork,vinpairs(obj.args)do iftype(k)=='string'then localprefix,num,suffix=k:match('^(.-)([1-9][0-9]*)(.*)$') ifprefixthen num=tonumber(num) prefixArgs[prefix]=prefixArgs[prefix]or{} prefixArgs[prefix][num]=prefixArgs[prefix][num]or{} prefixArgs[prefix][num][suffix]=v prefixArgs[prefix][num][1]=num end end end -- Remove the gaps localprefixArrays={} forprefix,prefixTableinpairs(prefixArgs)do prefixArrays[prefix]={} localnumKeys={} fornuminpairs(prefixTable)do numKeys[#numKeys+1]=num end table.sort(numKeys) for_,numinipairs(numKeys)do table.insert(prefixArrays[prefix],prefixTable[num]) end end obj.prefixArgs=prefixArrays end returnobj end functionArticleHistory:try(func,...) ifDEBUG_MODEthen localval=func(...) returnval else localsuccess,val=pcall(func,...) ifsuccessthen returnval else table.insert(self._errors,val) returnnil end end end functionArticleHistory:getActionObjects() -- Gets an array of action objects for the parameters specified by the -- user. We memoise this so that the parameters only have to be processed -- once. ifself.actionsthen returnself.actions end -- Get the action args, and exit if they don't exist. localactionArgs=self.prefixArgs[self.cfg.actionParamPrefix] ifnotactionArgsthen self.actions={} returnself.actions end -- Make the objects. localactions={} localsuffixes=self.cfg.actionParamSuffixes for_,tinipairs(actionArgs)do localobjArgs={} fork,vinpairs(t)do localnewK=suffixes[k] ifnewKthen objArgs[newK]=v end end objArgs.paramNum=t[1] objArgs.cfg=self.cfg objArgs.currentTitle=self.currentTitle localactionObj=self:try(Action.new,objArgs) table.insert(actions,actionObj) end self.actions=actions returnactions end functionArticleHistory:getStatusIdForCode(code) -- Gets a status ID given a status code. If no code is specified, returns -- nil, and if the code is invalid, raises an error. ifnotcodethen returnnil end localstatuses=self.cfg.statuses localcodeUpper=mw.ustring.upper(code) ifstatuses[codeUpper]then returnstatuses[codeUpper].id else self:addWarning( self:message('articlehistory-warning-invalid-status',code), self:message('articlehistory-warning-invalid-status-help') ) returnnil end end functionArticleHistory:getStatusObj() -- Get the status object for the current status. ifself.statusObj==falsethen returnnil elseifself.statusObj~=nilthen returnself.statusObj end localstatusId ifself.cfg.getStatusIdFunctionthen statusId=self:try(self.cfg.getStatusIdFunction,self) else statusId=self:try( self.getStatusIdForCode,self, self.args[self.cfg.currentStatusParam] ) end ifnotstatusIdthen self.statusObj=false returnnil end -- Check that some actions were specified, and if not add a warning. localactions=self:getActionObjects() if#actions<1then self:addWarning( self:message('articlehistory-warning-status-no-actions'), self:message('articlehistory-warning-status-no-actions-help') ) end -- Make a new status object. localstatusObjData={ id=statusId, currentTitle=self.currentTitle, cfg=self.cfg } localisMulti=self.cfg.statuses[statusId].isMulti localinitFunc=isMultiandMultiStatus.neworStatus.new localstatusObj=self:try(initFunc,statusObjData) self.statusObj=statusObjorfalse returnself.statusObjornil end functionArticleHistory:getStatusId() localstatusObj=self:getStatusObj() returnstatusObjandstatusObj.id end functionArticleHistory:_noticeFactory(memoizeKey,configKey,class) -- This holds the logic for fetching tables of Notice and CollapsibleNotice -- objects. ifself[memoizeKey]then returnself[memoizeKey] end localret={} for_,tinipairs(self.cfg[configKey]or{})do ift.isActive(self)then localdata={} fork,vinpairs(t)do ifk~='isActive'then data[k]=v end end data.cfg=self.cfg data.currentTitle=self.currentTitle ret[#ret+1]=class.new(data) end end self[memoizeKey]=ret returnret end functionArticleHistory:getNoticeObjects() returnself:_noticeFactory('notices','notices',Notice) end functionArticleHistory:getCollapsibleNoticeObjects() returnself:_noticeFactory( 'collapsibleNotices', 'collapsibleNotices', CollapsibleNotice ) end functionArticleHistory:getAllObjects(addSelf) localcacheKey=addSelfand'addSelf'or'default' localret=self._allObjectsCache[cacheKey] ifnotretthen ret={} localstatusObj=self:getStatusObj() ifstatusObjthen ret[#ret+1]=statusObj end localobjTables={ self:getNoticeObjects(), self:getActionObjects(), self:getCollapsibleNoticeObjects() } for_,tinipairs(objTables)do for_,objinipairs(t)do ret[#ret+1]=obj end end ifaddSelfthen ret[#ret+1]=self end self._allObjectsCache[cacheKey]=ret end returnret end functionArticleHistory:getNoticeBarIcons() localret={} -- Icons that aren't part of a row. ifself.cfg.noticeBarIconsthen for_,datainipairs(self.cfg.noticeBarIcons)do ifdata.isActive(self)then ret[#ret+1]=renderImage( data.icon, nil, data.sizeorself.cfg.defaultNoticeBarIconSize ) end end end -- Icons in row objects. for_,objinipairs(self:getAllObjects())do ret[#ret+1]=obj:exportNoticeBarIcon(self) end returnret end functionArticleHistory:getErrorMessages() -- Returns an array of error/warning strings. Error strings come first. localret={} for_,msginipairs(self._errors)do ret[#ret+1]=msg end for_,objinipairs(self:getAllObjects(true))do for_,msginipairs(obj:getWarnings())do ret[#ret+1]=msg end end returnret end functionArticleHistory:categoriesAreActive() -- Returns a boolean indicating whether categories should be output or not. localtitle=self.currentTitle localns=title.namespace returntitle.isTalkPage andns~=3-- not user talk andns~=119-- not draft talk end functionArticleHistory:renderCategories() localret={} ifself:categoriesAreActive()then -- Child object categories for_,objinipairs(self:getAllObjects())do localcategories=self:try(obj.getCategories,obj,self) for_,categoryObjinipairs(categoriesor{})do ret[#ret+1]=tostring(categoryObj) end end -- Extra categories for_,funcinipairs(self.cfg.extraCategoriesor{})do localcats=func(self)or{} for_,categoryObjinipairs(cats)do ret[#ret+1]=tostring(categoryObj) end end end returntable.concat(ret) end functionArticleHistory:__tostring() localroot=mw.html.create() -- Table root localtableRoot=root:tag('table') tableRoot:addClass('article-history tmbox tmbox-notice') -- Status localstatusObj=self:getStatusObj() ifstatusObjthen tableRoot:node(self:try(statusObj.exportHtml,statusObj,self)) end -- Notices localnotices=self:getNoticeObjects() for_,noticeObjinipairs(notices)do tableRoot:node(self:try(noticeObj.exportHtml,noticeObj,self)) end -- Get action objects and the collapsible notice objects, and generate the -- HTML objects for the action objects. We need the action HTML objects so -- that we can accurately calculate the number of collapsible rows, as some -- action objects may generate errors when the HTML is generated. localactions=self:getActionObjects()or{} localcollapsibleNotices=self:getCollapsibleNoticeObjects()or{} localcollapsibleNoticeHtmlObjects,actionHtmlObjects={},{} for_,objinipairs(actions)do table.insert( actionHtmlObjects, self:try(obj.exportHtml,obj,self) ) end for_,objinipairs(collapsibleNotices)do table.insert( collapsibleNoticeHtmlObjects, self:try(obj.exportHtml,obj,self,true)-- Render the collapsed version ) end localnActionRows=#actionHtmlObjects localnCollapsibleRows=nActionRows+#collapsibleNoticeHtmlObjects -- Find out if we are collapsed or not. localisCollapsed=yesno(self.args.collapse) ifisCollapsed==nilthen ifself.cfg.uncollapsedRows=='all'then isCollapsed=false elseifnCollapsibleRows==1then isCollapsed=false else isCollapsed=nCollapsibleRows>(tonumber(self.cfg.uncollapsedRows)or3) end end -- If we are not collapsed, re-render the collapsible notices in the -- non-collapsed version. ifnotisCollapsedthen collapsibleNoticeHtmlObjects={} for_,objinipairs(collapsibleNotices)do table.insert( collapsibleNoticeHtmlObjects, self:try(obj.exportHtml,obj,self,false) ) end end -- Collapsible table for actions and collapsible notices. Collapsible -- notices are only included in the table if it is collapsed. Action rows -- are always included. localcollapsibleTable ifisCollapsedornActionRows>0then -- Collapsible table base collapsibleTable=tableRoot :tag('tr') :tag('td') :attr('colspan',2) :css('width','100%') :tag('table') :addClass('article-history-milestones') :addClass(isCollapsedand'mw-collapsible mw-collapsed'ornil) :css('width','100%') :css('font-size','90%') -- Header row localctHeader=collapsibleTable :tag('tr') :tag('th') :attr('colspan',3) :css('font-size','110%') -- Notice bar ifisCollapsedthen localnoticeBarIcons=self:getNoticeBarIcons() if#noticeBarIcons>0then localnoticeBar=ctHeader:tag('span'):css('float','left') for_,iconinipairs(noticeBarIcons)do noticeBar:wikitext(icon) end ctHeader:wikitext(' ') end end -- Header text ifmw.site.namespaces[self.currentTitle.namespace].subject.id==0then ctHeader:wikitext(self:message('milestones-header')) else ctHeader:wikitext(self:message( 'milestones-header-other-ns', self.currentTitle.subjectNsText )) end -- Subheadings ifnActionRows>0then collapsibleTable :tag('tr') :css('text-align','left') :tag('th') :wikitext(self:message('milestones-date-header')) :done() :tag('th') :wikitext(self:message('milestones-process-header')) :done() :tag('th') :wikitext(self:message('milestones-result-header')) end -- Actions for_,htmlObjinipairs(actionHtmlObjects)do collapsibleTable:node(htmlObj) end end -- Collapsible notices and current status -- These are only included in the collapsible table if it is collapsed. -- Otherwise, they are added afterwards, so that they align with the -- notices. do localtableNode,statusColspan ifisCollapsedthen tableNode=collapsibleTable statusColspan=3 else tableNode=tableRoot statusColspan=2 end -- Collapsible notices for_,objinipairs(collapsibleNotices)do tableNode:node(self:try(obj.exportHtml,obj,self,isCollapsed)) end -- Current status ifstatusObjandnActionRows>1then tableNode :tag('tr') :tag('td') :attr('colspan',statusColspan) :wikitext(self:message('status-blurb',statusObj.name)) end end -- Get the categories. We have to do this before the error row, so that -- category errors display. localcategories=self:renderCategories() -- Error row and error category localerrors=self:getErrorMessages() localerrorCategory if#errors>0then localerrorList=tableRoot :tag('tr') :tag('td') :attr('colspan',2) :addClass('mbox-text') :tag('ul') :addClass('error') :css('font-weight','bold') for_,msginipairs(errors)do errorList:tag('li'):wikitext(msg) end ifself:categoriesAreActive()then errorCategory=tostring(Category.new(self:message( 'error-category' ))) end -- If there are no errors and no active objects, then exit. We can't make -- this check earlier as we don't know where the errors may be until we -- have finished rendering the banner. elseif#self:getAllObjects()<1then return'' end -- Add the categories root:wikitext(categories) root:wikitext(errorCategory) localframe=mw.getCurrentFrame() returnframe:extensionTag{ name='templatestyles',args={src='Modul:Message box/tmbox.css'} }..frame:extensionTag{ name='templatestyles',args={src='Modul:Article history/styles.css'} }..tostring(root) end ------------------------------------------------------------------------------- -- Exports -- These functions are called from Lua and from wikitext. ------------------------------------------------------------------------------- localp={} functionp._main(args,cfg,currentTitle) localarticleHistoryObj=ArticleHistory.new(args,cfg,currentTitle) returntostring(articleHistoryObj) end functionp.main(frame) localargs=require('Modul:Arguments').getArgs(frame,{ wrappers=WRAPPER_TEMPLATE }) ifframe:getTitle():find('sandbox',1,true)then CONFIG_PAGE=CONFIG_PAGE..'/bak pasir' end returnp._main(args) end functionp._exportClasses() return{ Message=Message, Row=Row, Status=Status, MultiStatus=MultiStatus, Notice=Notice, Action=Action, CollapsibleNotice=CollapsibleNotice, ArticleHistory=ArticleHistory } end returnp