sitewide CSS/JS (and editing other users' CSS/JS). No other group has
'editsitecss', 'editusercss', 'editsitejs' or 'edituserjs' by default.
* A new grant group, 'editsiteconfig', is added for granting the above rights.
+* The $wgPasswordSenderName setting, ignored since 1.23 by MediaWiki and almost
+ all extensions, is no longer set at all. Instead, you can modify the system
+ message `emailsender`.
=== New features in 1.32 ===
* (T112474) Generalized the ResourceLoader mechanism for overriding modules
additional links to the subtitle of a history page.
* The 'GetLinkColours' hook now receives an additional $title parameter,
the Title object of the page being parsed, on which the links will be shown.
+* (T194731) DifferenceEngine supports multiple slots. Added SlotDiffRenderer to
+ render diffs between two Content objects, and DifferenceEngine::setRevisions()
+ to render diffs between two custom (potentially multi-content) revisions.
+ Added GetSlotDiffRenderer hook which works like GetDifferenceEngine for slots.
=== External library changes in 1.32 ===
* …
Set $wgShowExceptionDetails and/or $wgShowHostnames instead.
* The $wgShowDBErrorBacktrace global is deprecated and nonfunctional.
Set $wgShowExceptionDetails instead.
-* Public access to the DifferenceEngine properties mOldid, mNewid, mOldPage,
- mNewPage, mOldContent, mNewContent, mRevisionsLoaded, mTextLoaded and
- mCacheHit is deprecated. Use getOldid() / getNewid() for the first two,
- do your own lookup for page/content. mNewRev / mOldRev remains public.
+* Public access to the DifferenceEngine properties mOldid, mNewid, mOldRev,
+ mNewRev, mOldPage, mNewPage, mOldContent, mNewContent, mRevisionsLoaded,
+ mTextLoaded and mCacheHit is deprecated. Use getOldid() / getNewid() /
+ getOldRevision() / getNewRevision() for the first four (note that the
+ revision ones return a RevisionRecord, not a Revision), do your own lookup
+ for page/content.
* The $wgExternalDiffEngine value 'wikidiff2' is deprecated. To use wikidiff2
just enable the PHP extension, and it will be autodetected.
+* (T194731) DifferenceEngine properties mOldContent and mNewContent and methods
+ setContent(), generateContentDiffBody(), generateTextDiffBody() and textDiff()
+ are deprecated. To interact with a single slot, use a SlotDiffRenderer (and
+ subclass it to customize diff rendering); to diff custom (e.g. unsaved)
+ content, use setRevisions(). Subclassing DifferenceEngine should only be done
+ to customize page-level diff properties (such as the navigation header).
* The wfUseMW function, soft-deprecated in 1.26, is now hard deprecated.
* All MagicWord static methods are now deprecated. Use the MagicWordFactory
methods instead.
* All SpecialPageFactory static methods are deprecated. Instead, call the
methods on a SpecialPageFactory instance, which may be obtained from
MediaWikiServices.
+* mw.user.stickyRandomId was renamed to the more explicit
+ mw.user.getPageviewToken to better capture its function.
=== Other changes in 1.32 ===
* (T198811) The following tables have had their UNIQUE indexes turned into
'ActiveUsersPager' => __DIR__ . '/includes/specials/pagers/ActiveUsersPager.php',
'ActivityUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/ActivityUpdateJob.php',
'ActorMigration' => __DIR__ . '/includes/ActorMigration.php',
+ 'AddChangeTag' => __DIR__ . '/maintenance/addChangeTag.php',
'AddRFCandPMIDInterwiki' => __DIR__ . '/maintenance/addRFCandPMIDInterwiki.php',
'AddSite' => __DIR__ . '/maintenance/addSite.php',
'AjaxDispatcher' => __DIR__ . '/includes/AjaxDispatcher.php',
'DiffOpCopy' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DiffOpDelete' => __DIR__ . '/includes/diff/DairikiDiff.php',
'DifferenceEngine' => __DIR__ . '/includes/diff/DifferenceEngine.php',
+ 'DifferenceEngineSlotDiffRenderer' => __DIR__ . '/includes/diff/DifferenceEngineSlotDiffRenderer.php',
'Digit2Html' => __DIR__ . '/maintenance/language/digit2html.php',
'DjVuHandler' => __DIR__ . '/includes/media/DjVuHandler.php',
'DjVuImage' => __DIR__ . '/includes/media/DjVuImage.php',
'Maintenance' => __DIR__ . '/maintenance/Maintenance.php',
'MakeTestEdits' => __DIR__ . '/maintenance/makeTestEdits.php',
'MalformedTitleException' => __DIR__ . '/includes/title/MalformedTitleException.php',
+ 'ManageForeignResources' => __DIR__ . '/maintenance/resources/manageForeignResources.php',
'ManageJobs' => __DIR__ . '/maintenance/manageJobs.php',
'ManualLogEntry' => __DIR__ . '/includes/logging/LogEntry.php',
'MapCacheLRU' => __DIR__ . '/includes/libs/MapCacheLRU.php',
'SkinFallbackTemplate' => __DIR__ . '/includes/skins/SkinFallbackTemplate.php',
'SkinTemplate' => __DIR__ . '/includes/skins/SkinTemplate.php',
'SlideshowImageGallery' => __DIR__ . '/includes/gallery/SlideshowImageGallery.php',
+ 'SlotDiffRenderer' => __DIR__ . '/includes/diff/SlotDiffRenderer.php',
'SpecialActiveUsers' => __DIR__ . '/includes/specials/SpecialActiveusers.php',
'SpecialAllMessages' => __DIR__ . '/includes/specials/SpecialAllMessages.php',
'SpecialAllMyUploads' => __DIR__ . '/includes/specials/SpecialMyRedirectPages.php',
'TextContent' => __DIR__ . '/includes/content/TextContent.php',
'TextContentHandler' => __DIR__ . '/includes/content/TextContentHandler.php',
'TextPassDumper' => __DIR__ . '/maintenance/dumpTextPass.php',
+ 'TextSlotDiffRenderer' => __DIR__ . '/includes/diff/TextSlotDiffRenderer.php',
'TextStatsOutput' => __DIR__ . '/maintenance/language/StatOutputs.php',
'TgConverter' => __DIR__ . '/languages/classes/LanguageTg.php',
'ThrottledError' => __DIR__ . '/includes/exception/ThrottledError.php',
&$sortkey: Sortkey to use.
'GetDifferenceEngine': Called when getting a new difference engine interface
-object Return false for valid object in $differenceEngine or true for the
-default difference engine.
+object. Can be used to decorate or replace the default difference engine.
$context: IContextSource context to be used for diff
$old: Revision ID to show and diff with
$new: Either a revision ID or one of the strings 'cur', 'prev' or 'next'
$refreshCache: If set, refreshes the diff cache
$unhide: If set, allow viewing deleted revs
-&$differenceEngine: output parameter, difference engine object to be used for
- diff
+&$differenceEngine: The difference engine object to be used for the diff
'GetDoubleUnderscoreIDs': Modify the list of behavior switch (double
underscore) magic words. Called by MagicWord.
$user: User whose preferences are being used to make timestamp
$lang: Language that will be used to render the timestamp
+'GetSlotDiffRenderer': Replace or wrap the standard SlotDiffRenderer for some
+content type.
+$contentHandler: ContentHandler for which the slot diff renderer is fetched.
+&$slotDiffRenderer: SlotDiffRenderer to change or replace.
+$context: IContextSource
+
'getUserPermissionsErrors': Add a permissions error when permissions errors are
checked for. Use instead of userCan for most cases. Return false if the user
can't do it, and populate $result with the reason in the form of
*/
$wgPasswordSender = false;
-/**
- * Sender name for e-mail notifications.
- *
- * @deprecated since 1.23; use the system message 'emailsender' instead.
- */
-$wgPasswordSenderName = 'MediaWiki Mail';
-
/**
* Reply-To address for e-mail notifications.
*
*/
$wgResourceLoaderValidateJS = true;
-/**
- * If set to true, statically-sourced (file-backed) JavaScript resources will
- * be parsed for validity before being bundled up into ResourceLoader modules.
- *
- * This can be helpful for development by providing better error messages in
- * default (non-debug) mode, but JavaScript parsing is slow and memory hungry
- * and may fail on large pre-bundled frameworks.
- */
-$wgResourceLoaderValidateStaticJS = false;
-
/**
* Whether ResourceLoader should attempt to persist modules in localStorage on
* browsers that support the Web Storage API.
* @return string of HTML representing a box.
*/
private static function messageBox( $html, $className, $heading = '' ) {
- if ( $heading ) {
+ if ( $heading !== '' ) {
$html = self::element( 'h2', [], $heading ) . $html;
}
return self::rawElement( 'div', [ 'class' => $className ], $html );
/** @var bool Boolean for initialisation on demand */
public $mRestrictionsLoaded = false;
- /** @var string Text form including namespace/interwiki, initialised on demand */
- protected $mPrefixedText = null;
+ /**
+ * Text form including namespace/interwiki, initialised on demand
+ *
+ * Only public to share cache with TitleFormatter
+ *
+ * @private
+ * @var string
+ */
+ public $prefixedText = null;
/** @var mixed Cached value for getTitleProtection (create protection) */
public $mTitleProtection;
* @return string The prefixed title, with spaces
*/
public function getPrefixedText() {
- if ( $this->mPrefixedText === null ) {
+ if ( $this->prefixedText === null ) {
$s = $this->prefix( $this->mTextform );
$s = strtr( $s, '_', ' ' );
- $this->mPrefixedText = $s;
+ $this->prefixedText = $s;
}
- return $this->mPrefixedText;
+ return $this->prefixedText;
}
/**
"apihelp-block-param-allowusertalk": "允許使用者編輯自己的對話頁面 (依據 <var>[[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> 的設定)。",
"apihelp-block-param-reblock": "若使用者已被封鎖,覆寫既有的封鎖設定值。",
"apihelp-block-param-watchuser": "監視使用者或 IP 位址的使用者頁面與對話頁面。",
+ "apihelp-block-param-tags": "在封鎖日誌裡更改套用到項目的標籤。",
"apihelp-block-example-ip-simple": "封鎖 IP 位址 <kbd>192.0.2.5</kbd> 三天,原因為 <kbd>First strike</kbd>。",
"apihelp-block-example-user-complex": "永久封鎖 IP 位址 <kbd>Vandal</kbd>,原因為 <kbd>Vandalism</kbd>。",
"apihelp-changeauthenticationdata-summary": "為目前使用者變更身分核對資料。",
+ "apihelp-changeauthenticationdata-example-password": "嘗試更改目前使用者的密碼至 <kbd>ExamplePassword</kbd>。",
"apihelp-checktoken-summary": "檢查來自 <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> 的密鑰有效性。",
"apihelp-checktoken-param-type": "要測試的密鑰類型。",
"apihelp-checktoken-param-token": "要測試的密鑰。",
"apihelp-feedrecentchanges-param-feedformat": "摘要格式。",
"apihelp-feedrecentchanges-param-namespace": "用於限制結果的命名空間。",
"apihelp-feedrecentchanges-param-invert": "除所選定者外的所有命名空間。",
+ "apihelp-feedrecentchanges-param-days": "用於限制結果的天數。",
"apihelp-feedrecentchanges-param-limit": "回傳的結果數量上限。",
+ "apihelp-feedrecentchanges-param-from": "顯示自那時以來的更改。",
"apihelp-feedrecentchanges-param-hideminor": "隱藏小編輯。",
"apihelp-feedrecentchanges-param-hidebots": "隱藏由機器人做的變更。",
"apihelp-feedrecentchanges-param-hideanons": "隱藏匿名使用者做的變更。",
"apihelp-login-example-login": "登入",
"apihelp-logout-summary": "登出並清除 session 資料。",
"apihelp-logout-example-logout": "登出當前使用者",
+ "apihelp-managetags-summary": "執行相關到更改標籤的管理任務。",
"apihelp-managetags-param-tags": "在標籤管理日誌裡更改套用到項目的標籤。",
"apihelp-mergehistory-summary": "合併頁面歷史",
"apihelp-mergehistory-param-reason": "合併歷史的原因。",
"apihelp-paraminfo-param-helpformat": "說明字串的格式。",
"apihelp-parse-param-summary": "解析摘要。",
"apihelp-parse-param-pageid": "解析此頁面的內容。覆蓋 <var>$1page</var>。",
+ "apihelp-parse-param-redirects": "若 <var>$1page</var> 或者 <var>$1pageid</var> 被設定成重新導向,則解析它。",
"apihelp-parse-param-prop": "要取得的資訊部份:",
"apihelp-parse-paramvalue-prop-headhtml": "取得頁面已解析的 <code><head></code>。",
"apihelp-parse-param-disablepp": "請改用<var>$1disablelimitreport</var>。",
"apihelp-purge-param-forcelinkupdate": "更新連結表格。",
"apihelp-purge-example-generator": "重新整理主要命名空間的前10個頁面。",
"apihelp-query-summary": "擷取來自及有關MediaWiki的數據。",
+ "apihelp-query-param-prop": "替已查詢頁面所要取得的屬性。",
"apihelp-query-param-list": "要取得的清單。",
"apihelp-query-param-meta": "要取得的詮釋資料。",
"apihelp-query+allcategories-summary": "列舉所有分類。",
"apihelp-query+alldeletedrevisions-param-user": "此列出由該使用者作出的修訂。",
"apihelp-query+alldeletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
"apihelp-query+alldeletedrevisions-param-namespace": "僅列出此命名空間的頁面。",
+ "apihelp-query+allfileusages-param-prop": "要包含到的資訊部份:",
"apihelp-query+allfileusages-paramvalue-prop-title": "添加檔案標題。",
"apihelp-query+allfileusages-param-limit": "要回傳的項目總數。",
"apihelp-query+allfileusages-param-dir": "列出時所採用的方向。",
"apihelp-query+allfileusages-example-unique": "列出唯一的檔案標題。",
"apihelp-query+allfileusages-example-unique-generator": "取得所有檔案標題,標記為遺失。",
"apihelp-query+allfileusages-example-generator": "取得包含檔案的頁面。",
+ "apihelp-query+allimages-summary": "按順序列舉所有圖片。",
"apihelp-query+allimages-param-sort": "作為排序順序的屬性。",
"apihelp-query+allimages-param-dir": "列出時所採用的方向。",
"apihelp-query+allimages-param-minsize": "限制圖片至少要有這樣多的位元組。",
"apihelp-query+allimages-param-maxsize": "限制圖片最多只能這樣多的位元組。",
+ "apihelp-query+allimages-param-sha1": "圖片的 SHA1 雜湊值。覆蓋 $1sha1base36。",
+ "apihelp-query+allimages-param-sha1base36": "以 base 36 的圖片 SHA1 雜湊值(使用在 MediaWiki)。",
"apihelp-query+allimages-param-mime": "所要搜尋的 MIME 類型,例如:<kbd>image/jpeg</kbd>。",
"apihelp-query+allimages-param-limit": "要回傳的圖片總數。",
+ "apihelp-query+alllinks-param-from": "要起始列舉的連結標題。",
+ "apihelp-query+alllinks-param-to": "要終止列舉的連結標題。",
+ "apihelp-query+alllinks-param-prop": "要包含的資訊部份:",
"apihelp-query+alllinks-paramvalue-prop-title": "添加連結標題。",
"apihelp-query+alllinks-param-namespace": "要列舉的命名空間。",
"apihelp-query+alllinks-param-limit": "要回傳的項目總數。",
"apihelp-query+alllinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+alllinks-example-unique-generator": "取得所有已連結標題,標記為遺失。",
+ "apihelp-query+alllinks-example-generator": "取得包含連結的頁面。",
"apihelp-query+allmessages-summary": "返回來自該網站的訊息。",
"apihelp-query+allmessages-param-prop": "要取得的屬性。",
"apihelp-query+allmessages-param-lang": "以此語言來回傳訊息。",
"apihelp-query+allmessages-param-from": "以此訊息來回傳訊息開頭。",
"apihelp-query+allmessages-param-to": "以此訊息來回傳訊息結尾。",
+ "apihelp-query+allmessages-param-prefix": "回傳帶有前綴的訊息。",
+ "apihelp-query+allmessages-example-ipb": "顯示以 <kbd>ipb-</kbd> 起始的訊息。",
+ "apihelp-query+allmessages-example-de": "顯示在德語裡的 <kbd>august</kbd> 與 <kbd>mainpage</kbd> 訊息。",
"apihelp-query+allpages-param-from": "起始列舉的頁面標題。",
"apihelp-query+allpages-param-to": "終止列舉的頁面標題。",
"apihelp-query+allpages-param-prefix": "搜尋以此值為開頭的所有頁面標題。",
"apihelp-query+allredirects-param-namespace": "要列舉的命名空間。",
"apihelp-query+allredirects-param-limit": "要回傳的項目總數。",
"apihelp-query+allredirects-param-dir": "列出時所採用的方向。",
+ "apihelp-query+allredirects-example-unique-generator": "取得所有目標頁面,標記為遺失。",
"apihelp-query+allredirects-example-generator": "取得包含重新導向的頁面。",
"apihelp-query+allrevisions-summary": "列出所有修訂版本。",
"apihelp-query+allrevisions-param-start": "起始列舉的時間戳記。",
"apihelp-query+backlinks-summary": "找出連結至指定頁面的所有頁面。",
"apihelp-query+backlinks-param-namespace": "要列舉的命名空間。",
"apihelp-query+backlinks-param-dir": "列出時所採用的方向。",
+ "apihelp-query+backlinks-example-simple": "顯示至 <kbd>Main page</kbd> 的連結。",
"apihelp-query+blocks-summary": "列出所有被封鎖使用者與 IP 位址。",
"apihelp-query+blocks-param-start": "起始列舉的時間戳記。",
"apihelp-query+blocks-param-end": "終止列舉的時間戳記。",
"apihelp-query+blocks-paramvalue-prop-byid": "添加進行封鎖中的使用者之使用者 ID。",
"apihelp-query+blocks-example-simple": "列出封鎖。",
"apihelp-query+blocks-example-users": "列出使用者 <kbd>Alice</kbd> 與 <kbd>Bob</kbd> 的封鎖。",
+ "apihelp-query+categories-summary": "列出頁面隸屬的所有分類。",
"apihelp-query+categories-param-limit": "要回傳的分類數量。",
"apihelp-query+categoryinfo-summary": "回傳有關指定分類的資訊。",
"apihelp-query+categorymembers-summary": "在指定的分類中列出所有頁面。",
"apihelp-query+categorymembers-paramvalue-prop-ids": "添加頁面 ID。",
"apihelp-query+categorymembers-param-limit": "回傳的頁面數量上限。",
+ "apihelp-query+categorymembers-param-sort": "作為排序順序的屬性。",
"apihelp-query+categorymembers-param-startsortkey": "請改用 $1starthexsortkey。",
"apihelp-query+categorymembers-param-endsortkey": "請改用 $1endhexsortkey。",
"apihelp-query+categorymembers-example-simple": "取得在 <kbd>Category:Physics</kbd> 裡前 10 項的頁面。",
"apihelp-query+contributors-param-limit": "要回傳的貢獻人員數量。",
"apihelp-query+deletedrevisions-summary": "取得已刪除修訂的資訊。",
+ "apihelp-query+deletedrevisions-param-tag": "僅列出以此標籤所標記的修訂。",
"apihelp-query+deletedrevisions-param-user": "此列出由該使用者作出的修訂。",
"apihelp-query+deletedrevisions-param-excludeuser": "不要列出由該使用者作出的修訂。",
"apihelp-query+deletedrevs-summary": "列出已刪除的修訂。",
"apihelp-query+embeddedin-param-limit": "要回傳的頁面總數。",
"apihelp-query+extlinks-summary": "回傳所有指定頁面的外部 URL (非 interwiki)。",
"apihelp-query+extlinks-param-limit": "要回傳的連結數量。",
+ "apihelp-query+exturlusage-param-prop": "要包含的資訊部份:",
"apihelp-query+exturlusage-paramvalue-prop-ids": "添加頁面 ID。",
+ "apihelp-query+exturlusage-paramvalue-prop-title": "添加標題與頁面的命名空間 ID。",
"apihelp-query+exturlusage-paramvalue-prop-url": "添加用於頁面的 URL。",
"apihelp-query+exturlusage-param-namespace": "要列舉的頁面命名空間。",
"apihelp-query+exturlusage-param-limit": "要回傳的頁面數量。",
+ "apihelp-query+filearchive-param-from": "起始列舉的圖片標題。",
+ "apihelp-query+filearchive-param-to": "終止列舉的圖片標題。",
"apihelp-query+filearchive-param-limit": "要回傳的圖片總數。",
"apihelp-query+filearchive-param-dir": "列出時所採用的方向。",
"apihelp-query+filearchive-param-sha1": "圖片的 SHA1 雜湊值。覆蓋 $1sha1base36。",
"apihelp-query+filearchive-param-prop": "要取得的圖片資訊:",
"apihelp-query+filearchive-paramvalue-prop-sha1": "替圖片添加 SHA-1 雜湊值。",
+ "apihelp-query+filearchive-paramvalue-prop-description": "添加圖片版本的描述。",
+ "apihelp-query+filearchive-paramvalue-prop-parseddescription": "解析版本的描述。",
"apihelp-query+filearchive-paramvalue-prop-mime": "添加圖片的 MIME。",
"apihelp-query+filearchive-paramvalue-prop-mediatype": "添加圖片的媒體類型。",
+ "apihelp-query+filearchive-paramvalue-prop-metadata": "列出圖片版本的 Exif 詮釋資料。",
+ "apihelp-query+filearchive-paramvalue-prop-bitdepth": "添加版本的位元深度。",
+ "apihelp-query+filearchive-example-simple": "顯示所有已刪除檔案的清單。",
+ "apihelp-query+filerepoinfo-paramvalue-prop-url": "公共區域 URL 路徑。",
"apihelp-query+fileusage-param-prop": "要取得的屬性。",
"apihelp-query+fileusage-paramvalue-prop-pageid": "各頁面的頁面 ID。",
"apihelp-query+fileusage-paramvalue-prop-title": "各頁面的標題。",
"apihelp-query+imageusage-param-dir": "列出時所採用的方向。",
"apihelp-query+info-summary": "取得基本頁面訊息。",
"apihelp-query+info-param-prop": "要取得的額外屬性:",
+ "apihelp-query+info-paramvalue-prop-protection": "列出各頁面的保護層級。",
"apihelp-query+info-paramvalue-prop-readable": "使用者是否可閱讀此頁面。",
"apihelp-query+iwbacklinks-param-prop": "要取得的屬性。",
"apihelp-query+iwlinks-summary": "回傳指定頁面的所有 interwiki 連結。",
"apihelp-query+iwlinks-paramvalue-prop-url": "添加完整的 URL。",
"apihelp-query+iwlinks-param-limit": "要回傳的跨 Wiki 連結數量。",
+ "apihelp-query+iwlinks-param-dir": "列出時所採用的方向。",
"apihelp-query+langbacklinks-param-limit": "要回傳的頁面總數。",
"apihelp-query+langbacklinks-param-prop": "要取得的屬性。",
+ "apihelp-query+langbacklinks-param-dir": "列出時所採用的方向。",
"apihelp-query+langlinks-summary": "回傳指定頁面的所有跨語言連結。",
"apihelp-query+langlinks-param-limit": "要回傳的 langlinks 數量。",
"apihelp-query+langlinks-paramvalue-prop-url": "添加完整的 URL。",
"apihelp-query+links-summary": "回傳指定頁面的所有連結。",
"apihelp-query+links-param-limit": "要回傳的連結數量。",
"apihelp-query+linkshere-param-prop": "要取得的屬性。",
+ "apihelp-query+linkshere-paramvalue-prop-pageid": "各頁面的頁面 ID。",
"apihelp-query+linkshere-paramvalue-prop-title": "各頁面的標題。",
"apihelp-query+linkshere-paramvalue-prop-redirect": "若頁面為重新導向,則做出標記。",
+ "apihelp-query+linkshere-param-namespace": "僅包含這些命名空間的頁面。",
"apihelp-query+linkshere-param-limit": "要回傳的數量。",
"apihelp-query+logevents-summary": "從日誌中獲取事件。",
"apihelp-query+logevents-param-prop": "要取得的屬性。",
"apihelp-query+logevents-param-end": "結束列舉的時間戳記。",
"apihelp-query+logevents-param-limit": "要回傳的事件項目總數。",
"apihelp-query+pagepropnames-param-limit": "回傳的名稱數量上限。",
+ "apihelp-query+pagepropnames-example-simple": "取得前 10 個屬性名稱。",
"apihelp-query+pageswithprop-paramvalue-prop-ids": "添加頁面 ID。",
"apihelp-query+pageswithprop-param-limit": "回傳的頁面數量上限。",
"apihelp-query+prefixsearch-param-search": "搜尋字串。",
"apihelp-query+prefixsearch-param-namespace": "搜尋的命名空間。若 <var>$1search</var> 以有效的命名空間前綴為開頭則會被忽略。",
"apihelp-query+prefixsearch-param-limit": "回傳的結果數量上限。",
"apihelp-query+prefixsearch-param-offset": "要略過的結果數量。",
+ "apihelp-query+protectedtitles-param-namespace": "僅列出這些命名空間的標題。",
+ "apihelp-query+protectedtitles-param-level": "僅列出具有這些保護層級的標題。",
"apihelp-query+protectedtitles-param-limit": "要回傳的頁面總數。",
"apihelp-query+protectedtitles-param-prop": "要取得的屬性。",
+ "apihelp-query+protectedtitles-paramvalue-prop-level": "添加保護層級。",
"apihelp-query+protectedtitles-example-simple": "列出已保護的標題。",
"apihelp-query+querypage-param-limit": "回傳的結果數量。",
"apihelp-query+random-summary": "取得隨機頁面集合",
"apihelp-query+redirects-param-prop": "要取得的屬性。",
"apihelp-query+redirects-paramvalue-prop-pageid": "各重新導向的頁面 ID。",
"apihelp-query+redirects-paramvalue-prop-title": "各重新導向的標題。",
+ "apihelp-query+redirects-param-namespace": "僅包含這些命名空間的頁面。",
"apihelp-query+redirects-param-limit": "要回傳的重新導向數量。",
"apihelp-query+revisions-summary": "取得修訂的資訊。",
"apihelp-query+revisions-example-content": "取得用於標題 <kbd>API</kbd> 與 <kbd>Main Page</kbd> 最新修訂內容的資料。",
"apihelp-query+search-paramvalue-prop-hasrelated": "已忽略",
"apihelp-query+search-param-limit": "要回傳的頁面總數。",
"apihelp-query+siteinfo-paramvalue-prop-general": "全面系統資訊。",
+ "apihelp-query+siteinfo-paramvalue-prop-specialpagealiases": "特殊頁面別名清單。",
"apihelp-query+siteinfo-param-numberingroup": "列出在使用者群組裡的使用者數目。",
"apihelp-query+siteinfo-example-simple": "索取站台資訊。",
"apihelp-query+siteinfo-example-interwiki": "索取本地端跨 wiki 前綴的清單。",
"apihelp-query+usercontribs-paramvalue-prop-comment": "添加編輯的註釋。",
"apihelp-query+usercontribs-paramvalue-prop-parsedcomment": "添加編輯的已解析註解。",
"apihelp-query+usercontribs-paramvalue-prop-size": "添加編輯的新大小。",
+ "apihelp-query+userinfo-summary": "取得目前使用者的資訊。",
+ "apihelp-query+userinfo-param-prop": "要包含的資訊部份:",
"apihelp-query+userinfo-paramvalue-prop-realname": "添加使用者的真實姓名。",
"apihelp-query+userinfo-paramvalue-prop-email": "添加使用者的電子郵件地址與電子郵件驗證日期。",
"apihelp-query+userinfo-paramvalue-prop-registrationdate": "添加使用者的註冊日期。",
}
$taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() );
- $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc );
- return $context->getLanguage()->truncateForVisual( $escapedDesc, $length );
+ return $context->getLanguage()->truncateForVisual( $taglessDesc, $length );
}
/**
* Extensions should NOT use this function; they can use the ListDefinedTags
* hook instead.
*
- * Includes a call to ChangeTag::canDeleteTag(), so your code doesn't need to
+ * Includes a call to ChangeTag::canCreateTag(), so your code doesn't need to
* do that.
*
* @param string $tag
<?php
+use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MediaWiki\Search\ParserOutputSearchDataExtractor;
/**
* Factory for creating an appropriate DifferenceEngine for this content model.
+ * Since 1.32, this is only used for page-level diffs; to diff two content objects,
+ * use getSlotDiffRenderer.
+ *
+ * The DifferenceEngine subclass to use is selected in getDiffEngineClass(). The
+ * GetDifferenceEngine hook will receive the DifferenceEngine object and can replace or
+ * wrap it.
+ * (Note that in older versions of MediaWiki the hook documentation instructed extensions
+ * to return false from the hook; you should not rely on always being able to decorate
+ * the DifferenceEngine instance from the hook. If the owner of the content type wants to
+ * decorare the instance, overriding this method is a safer approach.)
+ *
+ * @todo This is page-level functionality so it should not belong to ContentHandler.
+ * Move it to a better place once one exists (e.g. PageTypeHandler).
*
* @since 1.21
*
$rcid = 0, // FIXME: Deprecated, no longer used
$refreshCache = false, $unhide = false
) {
- // hook: get difference engine
- $differenceEngine = null;
- if ( !Hooks::run( 'GetDifferenceEngine',
- [ $context, $old, $new, $refreshCache, $unhide, &$differenceEngine ]
- ) ) {
- return $differenceEngine;
- }
$diffEngineClass = $this->getDiffEngineClass();
- return new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+ $differenceEngine = new $diffEngineClass( $context, $old, $new, $rcid, $refreshCache, $unhide );
+ Hooks::run( 'GetDifferenceEngine', [ $context, $old, $new, $refreshCache, $unhide,
+ &$differenceEngine ] );
+ return $differenceEngine;
+ }
+
+ /**
+ * Get an appropriate SlotDiffRenderer for this content model.
+ * @since 1.32
+ * @param IContextSource $context
+ * @return SlotDiffRenderer
+ */
+ final public function getSlotDiffRenderer( IContextSource $context ) {
+ $slotDiffRenderer = $this->getSlotDiffRendererInternal( $context );
+ if ( get_class( $slotDiffRenderer ) === TextSlotDiffRenderer::class ) {
+ // To keep B/C, when SlotDiffRenderer is not overridden for a given content type
+ // but DifferenceEngine is, use that instead.
+ $differenceEngine = $this->createDifferenceEngine( $context );
+ if ( get_class( $differenceEngine ) !== DifferenceEngine::class ) {
+ // TODO turn this into a deprecation warning in a later release
+ LoggerFactory::getInstance( 'diff' )->notice(
+ 'Falling back to DifferenceEngineSlotDiffRenderer', [
+ 'modelID' => $this->getModelID(),
+ 'DifferenceEngine' => get_class( $differenceEngine ),
+ ] );
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ }
+ }
+ Hooks::run( 'GetSlotDiffRenderer', [ $this, &$slotDiffRenderer, $context ] );
+ return $slotDiffRenderer;
+ }
+
+ /**
+ * Return the SlotDiffRenderer appropriate for this content handler.
+ * @param IContextSource $context
+ * @return SlotDiffRenderer
+ */
+ protected function getSlotDiffRendererInternal( IContextSource $context ) {
+ $contentLanguage = MediaWikiServices::getInstance()->getContentLanguage();
+ $statsdDataFactory = MediaWikiServices::getInstance()->getStatsdDataFactory();
+ $slotDiffRenderer = new TextSlotDiffRenderer();
+ $slotDiffRenderer->setStatsdDataFactory( $statsdDataFactory );
+ // XXX using the page language would be better, but it's unclear how that should be injected
+ $slotDiffRenderer->setLanguage( $contentLanguage );
+ $slotDiffRenderer->setWikiDiff2MovedParagraphDetectionCutoff(
+ $context->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' )
+ );
+
+ $engine = DifferenceEngine::getEngine();
+ if ( $engine === false ) {
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
+ } elseif ( $engine === 'wikidiff2' ) {
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_WIKIDIFF2 );
+ } else {
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_EXTERNAL, $engine );
+ }
+
+ return $slotDiffRenderer;
}
/**
return false;
}
- /**
- * Usually aborts on failure
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws DBConnectionError
- * @return resource|null
- */
function open( $server, $user, $password, $dbName ) {
global $wgDBOracleDRCP;
if ( !function_exists( 'oci_connect' ) ) {
$this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
$this->doQuery( 'ALTER SESSION SET NLS_NUMERIC_CHARACTERS=\'.,\'' );
- return $this->conn;
+ return (bool)$this->conn;
}
/**
* @file
* @ingroup DifferenceEngine
*/
-use MediaWiki\MediaWikiServices;
-use MediaWiki\Shell\Shell;
+
+use MediaWiki\Storage\RevisionRecord;
/**
- * @todo document
+ * DifferenceEngine is responsible for rendering the difference between two revisions as HTML.
+ * This includes interpreting URL parameters, retrieving revision data, checking access permissions,
+ * selecting and invoking the diff generator class for the individual slots, doing post-processing
+ * on the generated diff, adding the rest of the HTML (such as headers) and writing the whole thing
+ * to OutputPage.
+ *
+ * DifferenceEngine can be subclassed by extensions, by customizing
+ * ContentHandler::createDifferenceEngine; the content handler will be selected based on the
+ * content model of the main slot (of the new revision, when the two are different).
+ * That might change after PageTypeHandler gets introduced.
+ *
+ * In the past, the class was also used for slot-level diff generation, and extensions might still
+ * subclass it and add such functionality. When that is the case (sepcifically, when a
+ * ContentHandler returns a standard SlotDiffRenderer but a nonstandard DifferenceEngine)
+ * DifferenceEngineSlotDiffRenderer will be used to convert the old behavior into the new one.
+ *
* @ingroup DifferenceEngine
+ *
+ * @todo This class is huge and poorly defined. It should be split into a controller responsible
+ * for interpreting query parameters, retrieving data and checking permissions; and a HTML renderer.
*/
class DifferenceEngine extends ContextSource {
private $mOldTags;
private $mNewTags;
- /** @var Content|null */
- protected $mOldContent;
-
- /** @var Content|null */
- protected $mNewContent;
+ /**
+ * Old revision (left pane).
+ * Allowed to be an unsaved revision, unlikely that's ever needed though.
+ * Null when the old revision does not exist; this can happen when using
+ * diff=prev on the first revision.
+ * Since 1.32 public access is deprecated.
+ * @var Revision|null
+ */
+ protected $mOldRev;
- /** @var Language */
- protected $mDiffLang;
+ /**
+ * New revision (right pane).
+ * Note that this might be an unsaved revision (e.g. for edit preview).
+ * Null only in case of load failure; diff methods will just return an error message in that case.
+ * Since 1.32 public access is deprecated.
+ * @var Revision|null
+ */
+ protected $mNewRev;
- /** @var Title */
+ /**
+ * Title of $mOldRev or null if the old revision does not exist or does not belong to a page.
+ * Since 1.32 public access is deprecated and the property can be null.
+ * @var Title|null
+ */
protected $mOldPage;
- /** @var Title */
+ /**
+ * Title of $mNewRev or null if the new revision does not exist or does not belong to a page.
+ * Since 1.32 public access is deprecated and the property can be null.
+ * @var Title|null
+ */
protected $mNewPage;
- /** @var Revision|null */
- public $mOldRev;
+ /**
+ * @var Content|null
+ * @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer.
+ * This property is set to the content of the main slot, but not actually used for the main diff.
+ */
+ private $mOldContent;
+
+ /**
+ * @var Content|null
+ * @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer.
+ * This property is set to the content of the main slot, but not actually used for the main diff.
+ */
+ private $mNewContent;
- /** @var Revision|null */
- public $mNewRev;
+ /** @var Language */
+ protected $mDiffLang;
/** @var bool Have the revisions IDs been loaded */
private $mRevisionsIdsLoaded = false;
/**
* Was the content overridden via setContent()?
- * If the content was overridden, most internal state (e.g. mOldid or mOldRev) should be ignored.
+ * If the content was overridden, most internal state (e.g. mOldid or mOldRev) should be ignored
+ * and only mOldContent and mNewContent is reliable.
+ * (Note that setRevisions() does not set this flag as in that case all properties are
+ * overriden and remain consistent with each other, so no special handling is needed.)
* @var bool
*/
protected $isContentOverridden = false;
/** @var bool Refresh the diff cache */
protected $mRefreshCache = false;
+ /** @var SlotDiffRenderer[] DifferenceEngine classes for the slots, keyed by role name. */
+ protected $slotDiffRenderers = null;
+
+ /**
+ * Temporary hack for B/C while slot diff related methods of DifferenceEngine are being
+ * deprecated. When true, we are inside a DifferenceEngineSlotDiffRenderer and
+ * $slotDiffRenderers should not be used.
+ * @var bool
+ */
+ protected $isSlotDiffRenderer = false;
+
/**#@-*/
/**
) {
$this->deprecatePublicProperty( 'mOldid', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mNewid', '1.32', __CLASS__ );
+ $this->deprecatePublicProperty( 'mOldRev', '1.32', __CLASS__ );
+ $this->deprecatePublicProperty( 'mNewRev', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mOldPage', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mNewPage', '1.32', __CLASS__ );
$this->deprecatePublicProperty( 'mOldContent', '1.32', __CLASS__ );
}
/**
+ * @return SlotDiffRenderer[] Diff renderers for each slot, keyed by role name.
+ * Includes slots only present in one of the revisions.
+ */
+ protected function getSlotDiffRenderers() {
+ if ( $this->isSlotDiffRenderer ) {
+ throw new LogicException( __METHOD__ . ' called in slot diff renderer mode' );
+ }
+
+ if ( $this->slotDiffRenderers === null ) {
+ if ( !$this->loadRevisionData() ) {
+ return [];
+ }
+
+ $slotContents = $this->getSlotContents();
+ $this->slotDiffRenderers = array_map( function ( $contents ) {
+ /** @var $content Content */
+ $content = $contents['new'] ?: $contents['old'];
+ return $content->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
+ }, $slotContents );
+ }
+ return $this->slotDiffRenderers;
+ }
+
+ /**
+ * Mark this DifferenceEngine as a slot renderer (as opposed to a page renderer).
+ * This is used in legacy mode when the DifferenceEngine is wrapped in a
+ * DifferenceEngineSlotDiffRenderer.
+ * @internal For use by DifferenceEngineSlotDiffRenderer only.
+ */
+ public function markAsSlotDiffRenderer() {
+ $this->isSlotDiffRenderer = true;
+ }
+
+ /**
+ * Get the old and new content objects for all slots.
+ * This method does not do any permission checks.
+ * @return array [ role => [ 'old' => SlotRecord, 'new' => SlotRecord ], ... ]
+ */
+ protected function getSlotContents() {
+ if ( $this->isContentOverridden ) {
+ return [
+ 'main' => [
+ 'old' => $this->mOldContent,
+ 'new' => $this->mNewContent,
+ ]
+ ];
+ }
+
+ $oldRev = $this->mOldRev->getRevisionRecord();
+ $newRev = $this->mNewRev->getRevisionRecord();
+ // The order here will determine the visual order of the diff. The current logic is
+ // changed first, then added, then deleted. This is ad hoc and should not be relied on
+ // - in the future we may want the ordering to depend on the page type.
+ $roles = array_merge( $newRev->getSlotRoles(), $oldRev->getSlotRoles() );
+ $oldSlots = $oldRev->getSlots()->getSlots();
+ $newSlots = $newRev->getSlots()->getSlots();
+
+ $slots = [];
+ foreach ( $roles as $role ) {
+ $slots[$role] = [
+ 'old' => isset( $oldSlots[$role] ) ? $oldSlots[$role]->getContent() : null,
+ 'new' => isset( $newSlots[$role] ) ? $newSlots[$role]->getContent() : null,
+ ];
+ }
+ // move main slot to front
+ if ( isset( $slots['main'] ) ) {
+ $slots = [ 'main' => $slots['main'] ] + $slots;
+ }
+ return $slots;
+ }
+
+ /**
+ * Set reduced line numbers mode.
+ * When set, line X is not displayed when X is 1, for example to increase readability and
+ * conserve space with many small diffs.
* @param bool $value
*/
public function setReducedLineNumbers( $value = true ) {
return $this->mNewid;
}
+ /**
+ * Get the left side of the diff.
+ * Could be null when the first revision of the page is diffed to 'prev' (or in the case of
+ * load failure).
+ * @return RevisionRecord|null
+ */
+ public function getOldRevision() {
+ return $this->mOldRev ? $this->mOldRev->getRevisionRecord() : null;
+ }
+
+ /**
+ * Get the right side of the diff.
+ * Should not be null but can still happen in the case of load failure.
+ * @return RevisionRecord|null
+ */
+ public function getNewRevision() {
+ return $this->mNewRev ? $this->mNewRev->getRevisionRecord() : null;
+ }
+
/**
* Look up a special:Undelete link to the given deleted revision id,
* as a workaround for being unable to load deleted diffs in currently.
}
$user = $this->getUser();
- $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
- if ( $this->mOldPage ) { # mOldPage might not be set, see below.
+ $permErrors = [];
+ if ( $this->mNewPage ) {
+ $permErrors = $this->mNewPage->getUserPermissionsErrors( 'read', $user );
+ }
+ if ( $this->mOldPage ) {
$permErrors = wfMergeErrorArrays( $permErrors,
$this->mOldPage->getUserPermissionsErrors( 'read', $user ) );
}
# a diff between a version V and its previous version V' AND the version V
# is the first version of that article. In that case, V' does not exist.
if ( $this->mOldRev === false ) {
- $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
+ if ( $this->mNewPage ) {
+ $out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
+ }
$samePage = true;
$oldHeader = '';
// Allow extensions to change the $oldHeader variable
} else {
Hooks::run( 'DiffViewHeader', [ $this, $this->mOldRev, $this->mNewRev ] );
- if ( $this->mNewPage->equals( $this->mOldPage ) ) {
+ if ( !$this->mOldPage || !$this->mNewPage ) {
+ // XXX say something to the user?
+ $samePage = false;
+ } elseif ( $this->mNewPage->equals( $this->mOldPage ) ) {
$out->setPageTitle( $this->msg( 'difference-title', $this->mNewPage->getPrefixedText() ) );
$samePage = true;
} else {
$samePage = false;
}
- if ( $samePage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
+ if ( $samePage && $this->mNewPage && $this->mNewPage->quickUserCan( 'edit', $user ) ) {
if ( $this->mNewRev->isCurrent() && $this->mNewPage->userCan( 'rollback', $user ) ) {
$rollbackLink = Linker::generateRollback( $this->mNewRev, $this->getContext() );
if ( $rollbackLink ) {
}
# Make "previous revision link"
- if ( $samePage && $this->mOldRev->getPrevious() ) {
+ if ( $samePage && $this->mOldPage && $this->mOldRev->getPrevious() ) {
$prevlink = Linker::linkKnown(
$this->mOldPage,
$this->msg( 'previousdiff' )->escaped(),
# Make "next revision link"
# Skip next link on the top revision
- if ( $samePage && !$this->mNewRev->isCurrent() ) {
+ if ( $samePage && $this->mNewPage && !$this->mNewRev->isCurrent() ) {
$nextlink = Linker::linkKnown(
$this->mNewPage,
$this->msg( 'nextdiff' )->escaped(),
if ( $this->mMarkPatrolledLink === null ) {
$linkInfo = $this->getMarkPatrolledLinkInfo();
// If false, there is no patrol link needed/allowed
- if ( !$linkInfo ) {
+ if ( !$linkInfo || !$this->mNewPage ) {
$this->mMarkPatrolledLink = '';
} else {
$this->mMarkPatrolledLink = ' <span class="patrollink" data-mw="interface">[' .
// Prepare a change patrol link, if applicable
if (
// Is patrolling enabled and the user allowed to?
- $wgUseRCPatrol && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
+ $wgUseRCPatrol && $this->mNewPage && $this->mNewPage->quickUserCan( 'patrol', $user ) &&
// Only do this if the revision isn't more than 6 hours older
// than the Max RC age (6h because the RC might not be cleaned out regularly)
RecentChange::isInRCLifespan( $this->mNewRev->getTimestamp(), 21600 )
# Page content may be handled by a hooked call instead...
if ( Hooks::run( 'ArticleContentOnDiff', [ $this, $out ] ) ) {
$this->loadNewText();
+ if ( !$this->mNewPage ) {
+ // New revision is unsaved; bail out.
+ // TODO in theory rendering the new revision is a meaningful thing to do
+ // even if it's unsaved, but a lot of untangling is required to do it safely.
+ }
+
$out->setRevisionId( $this->mNewid );
$out->setRevisionTimestamp( $this->mNewRev->getTimestamp() );
$out->setArticleFlag( true );
* Add style sheets for diff display.
*/
public function showDiffStyle() {
- $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
+ if ( !$this->isSlotDiffRenderer ) {
+ $this->getOutput()->addModuleStyles( 'mediawiki.diff.styles' );
+ foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
+ $slotDiffRenderer->addModules( $this->getOutput() );
+ }
+ }
}
/**
public function getDiffBody() {
$this->mCacheHit = true;
// Check if the diff should be hidden from this user
- if ( !$this->loadRevisionData() ) {
- return false;
- } elseif ( $this->mOldRev &&
- !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
- ) {
- return false;
- } elseif ( $this->mNewRev &&
- !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
- ) {
- return false;
- }
- // Short-circuit
- if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev
- && $this->mOldRev->getId() == $this->mNewRev->getId() )
- ) {
- if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
- return '';
+ if ( !$this->isContentOverridden ) {
+ if ( !$this->loadRevisionData() ) {
+ return false;
+ } elseif ( $this->mOldRev &&
+ !$this->mOldRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
+ return false;
+ } elseif ( $this->mNewRev &&
+ !$this->mNewRev->userCan( Revision::DELETED_TEXT, $this->getUser() )
+ ) {
+ return false;
+ }
+ // Short-circuit
+ if ( $this->mOldRev === false || ( $this->mOldRev && $this->mNewRev &&
+ $this->mOldRev->getId() && $this->mOldRev->getId() == $this->mNewRev->getId() )
+ ) {
+ if ( Hooks::run( 'DifferenceEngineShowEmptyOldContent', [ $this ] ) ) {
+ return '';
+ }
}
}
+
// Cacheable?
$key = false;
$cache = ObjectCache::getMainWANInstance();
return false;
}
- $difftext = $this->generateContentDiffBody( $this->mOldContent, $this->mNewContent );
+ $difftext = '';
+ // We've checked for revdelete at the beginning of this method; it's OK to ignore
+ // read permissions here.
+ $slotContents = $this->getSlotContents();
+ foreach ( $this->getSlotDiffRenderers() as $role => $slotDiffRenderer ) {
+ $slotDiff = $slotDiffRenderer->getDiff( $slotContents[$role]['old'],
+ $slotContents[$role]['new'] );
+ if ( $slotDiff && $role !== 'main' ) {
+ // TODO use human-readable role name at least
+ $slotTitle = $role;
+ $difftext .= $this->getSlotHeader( $slotTitle );
+ }
+ $difftext .= $slotDiff;
+ }
// Avoid PHP 7.1 warning from passing $this by reference
$diffEngine = $this;
return $difftext;
}
+ /**
+ * Get a slot header for inclusion in a diff body (as a table row).
+ *
+ * @param string $headerText The text of the header
+ * @return string
+ *
+ */
+ protected function getSlotHeader( $headerText ) {
+ // The old revision is missing on oldid=<first>&diff=prev; only 2 columns in that case.
+ $columnCount = $this->mOldRev ? 4 : 2;
+ $userLang = $this->getLanguage()->getHtmlCode();
+ return Html::rawElement( 'tr', [ 'class' => 'mw-diff-slot-header', 'lang' => $userLang ],
+ Html::element( 'th', [ 'colspan' => $columnCount ], $headerText ) );
+ }
+
/**
* Returns the cache key for diff body text or content.
*
$params[] = $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' );
}
+ if ( !$this->isSlotDiffRenderer ) {
+ foreach ( $this->getSlotDiffRenderers() as $slotDiffRenderer ) {
+ $params = array_merge( $params, $slotDiffRenderer->getExtraCacheKeys() );
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Implements DifferenceEngineSlotDiffRenderer::getExtraCacheKeys(). Only used when
+ * DifferenceEngine is wrapped in DifferenceEngineSlotDiffRenderer.
+ * @return array
+ * @internal for use by DifferenceEngineSlotDiffRenderer only
+ * @deprecated
+ */
+ public function getExtraCacheKeys() {
+ // This method is called when the DifferenceEngine is used for a slot diff. We only care
+ // about special things, not the revision IDs, which are added to the cache key by the
+ // page-level DifferenceEngine, and which might not have a valid value for this object.
+ $this->mOldid = 123456789;
+ $this->mNewid = 987654321;
+
+ // This will repeat a bunch of unnecessary key fields for each slot. Not nice but harmless.
+ $cacheString = $this->getDiffBodyCacheKey();
+ if ( $cacheString ) {
+ return [ $cacheString ];
+ }
+
+ $params = $this->getDiffBodyCacheKeyParams();
+
+ // Try to get rid of the standard keys to keep the cache key human-readable:
+ // call the getDiffBodyCacheKeyParams implementation of the base class, and if
+ // the child class includes the same keys, drop them.
+ // Uses an obscure PHP feature where static calls to non-static methods are allowed
+ // as long as we are already in a non-static method of the same class, and the call context
+ // ($this) will be inherited.
+ // phpcs:ignore Squiz.Classes.SelfMemberReference.NotUsed
+ $standardParams = DifferenceEngine::getDiffBodyCacheKeyParams();
+ if ( array_slice( $params, 0, count( $standardParams ) ) === $standardParams ) {
+ $params = array_slice( $params, count( $standardParams ) );
+ }
+
return $params;
}
/**
* Generate a diff, no caching.
*
- * This implementation uses generateTextDiffBody() to generate a diff based on the default
- * serialization of the given Content objects. This will fail if $old or $new are not
- * instances of TextContent.
- *
- * Subclasses may override this to provide a different rendering for the diff,
- * perhaps taking advantage of the content's native form. This is required for all content
- * models that are not text based.
- *
* @since 1.21
*
* @param Content $old Old content
* @param Content $new New content
*
- * @throws MWException If old or new content is not an instance of TextContent.
+ * @throws Exception If old or new content is not an instance of TextContent.
* @return bool|string
+ *
+ * @deprecated since 1.32, use a SlotDiffRenderer instead.
*/
public function generateContentDiffBody( Content $old, Content $new ) {
- if ( !( $old instanceof TextContent ) ) {
- throw new MWException( "Diff not implemented for " . get_class( $old ) . "; " .
- "override generateContentDiffBody to fix this." );
- }
-
- if ( !( $new instanceof TextContent ) ) {
- throw new MWException( "Diff not implemented for " . get_class( $new ) . "; "
- . "override generateContentDiffBody to fix this." );
- }
-
- $otext = $old->serialize();
- $ntext = $new->serialize();
-
- return $this->generateTextDiffBody( $otext, $ntext );
+ $slotDiffRenderer = $new->getContentHandler()->getSlotDiffRenderer( $this->getContext() );
+ if (
+ $slotDiffRenderer instanceof DifferenceEngineSlotDiffRenderer
+ && $this->isSlotDiffRenderer
+ ) {
+ // Oops, we are just about to enter an infinite loop (the slot-level DifferenceEngine
+ // called a DifferenceEngineSlotDiffRenderer that wraps the same DifferenceEngine class).
+ // This will happen when a content model has no custom slot diff renderer, it does have
+ // a custom difference engine, but that does not override this method.
+ throw new Exception( get_class( $this ) . ': could not maintain backwards compatibility. '
+ . 'Please use a SlotDiffRenderer.' );
+ }
+ return $slotDiffRenderer->getDiff( $old, $new ) . $this->getDebugString();
}
/**
* Generate a diff, no caching
*
- * @todo move this to TextDifferenceEngine, make DifferenceEngine abstract. At some point.
- *
* @param string $otext Old text, must be already segmented
* @param string $ntext New text, must be already segmented
*
+ * @throws Exception If content handling for text content is configured in a way
+ * that makes maintaining B/C hard.
* @return bool|string
+ *
+ * @deprecated since 1.32, use a TextSlotDiffRenderer instead.
*/
public function generateTextDiffBody( $otext, $ntext ) {
- $diff = function () use ( $otext, $ntext ) {
- $time = microtime( true );
-
- $result = $this->textDiff( $otext, $ntext );
-
- $time = intval( ( microtime( true ) - $time ) * 1000 );
- MediaWikiServices::getInstance()->getStatsdDataFactory()->timing( 'diff_time', $time );
- // Log requests slower than 99th percentile
- if ( $time > 100 && $this->mOldPage && $this->mNewPage ) {
- wfDebugLog( 'diff',
- "$time ms diff: {$this->mOldid} -> {$this->mNewid} {$this->mNewPage}" );
- }
-
- return $result;
- };
-
- /**
- * @param Status $status
- * @throws FatalError
- */
- $error = function ( $status ) {
- throw new FatalError( $status->getWikiText() );
- };
-
- // Use PoolCounter if the diff looks like it can be expensive
- if ( strlen( $otext ) + strlen( $ntext ) > 20000 ) {
- $work = new PoolCounterWorkViaCallback( 'diff',
- md5( $otext ) . md5( $ntext ),
- [ 'doWork' => $diff, 'error' => $error ]
- );
- return $work->execute();
- }
-
- return $diff();
+ $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ ->getSlotDiffRenderer( $this->getContext() );
+ if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) {
+ // Someone used the GetSlotDiffRenderer hook to replace the renderer.
+ // This is too unlikely to happen to bother handling properly.
+ throw new Exception( 'The slot diff renderer for text content should be a '
+ . 'TextSlotDiffRenderer subclass' );
+ }
+ return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
}
/**
* Process $wgExternalDiffEngine and get a sane, usable engine
*
* @return bool|string 'wikidiff2', path to an executable, or false
+ * @internal For use by this class and TextSlotDiffRenderer only.
*/
- private function getEngine() {
+ public static function getEngine() {
global $wgExternalDiffEngine;
// We use the global here instead of Config because we write to the value,
// and Config is not mutable.
*
* @param string $otext Old text, must be already segmented
* @param string $ntext New text, must be already segmented
+ *
+ * @throws Exception If content handling for text content is configured in a way
+ * that makes maintaining B/C hard.
* @return bool|string
+ *
+ * @deprecated since 1.32, use a TextSlotDiffRenderer instead.
*/
protected function textDiff( $otext, $ntext ) {
- $otext = str_replace( "\r\n", "\n", $otext );
- $ntext = str_replace( "\r\n", "\n", $ntext );
-
- $engine = $this->getEngine();
-
- // Better external diff engine, the 2 may some day be dropped
- // This one does the escaping and segmenting itself
- if ( $engine === 'wikidiff2' ) {
- $wikidiff2Version = phpversion( 'wikidiff2' );
- if (
- $wikidiff2Version !== false &&
- version_compare( $wikidiff2Version, '1.5.0', '>=' )
- ) {
- $text = wikidiff2_do_diff(
- $otext,
- $ntext,
- 2,
- $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' )
- );
- } else {
- // Don't pass the 4th parameter for compatibility with older versions of wikidiff2
- $text = wikidiff2_do_diff(
- $otext,
- $ntext,
- 2
- );
-
- // Log a warning in case the configuration value is set to not silently ignore it
- if ( $this->getConfig()->get( 'WikiDiff2MovedParagraphDetectionCutoff' ) > 0 ) {
- wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no
- effect since the used version of WikiDiff2 does not support it.' );
- }
- }
-
- $text .= $this->debug( 'wikidiff2' );
-
- return $text;
- } elseif ( $engine !== false ) {
- # Diff via the shell
- $tmpDir = wfTempDir();
- $tempName1 = tempnam( $tmpDir, 'diff_' );
- $tempName2 = tempnam( $tmpDir, 'diff_' );
-
- $tempFile1 = fopen( $tempName1, "w" );
- if ( !$tempFile1 ) {
- return false;
- }
- $tempFile2 = fopen( $tempName2, "w" );
- if ( !$tempFile2 ) {
- return false;
- }
- fwrite( $tempFile1, $otext );
- fwrite( $tempFile2, $ntext );
- fclose( $tempFile1 );
- fclose( $tempFile2 );
- $cmd = [ $engine, $tempName1, $tempName2 ];
- $result = Shell::command( $cmd )
- ->execute();
- $exitCode = $result->getExitCode();
- if ( $exitCode !== 0 ) {
- throw new Exception( "External diff command returned code {$exitCode}. Stderr: "
- . wfEscapeWikiText( $result->getStderr() )
- );
- }
- $difftext = $result->getStdout();
- $difftext .= $this->debug( "external $engine" );
- unlink( $tempName1 );
- unlink( $tempName2 );
-
- return $difftext;
- }
-
- # Native PHP diff
- $contLang = MediaWikiServices::getInstance()->getContentLanguage();
- $ota = explode( "\n", $contLang->segmentForDiff( $otext ) );
- $nta = explode( "\n", $contLang->segmentForDiff( $ntext ) );
- $diffs = new Diff( $ota, $nta );
- $formatter = new TableDiffFormatter();
- $difftext = $contLang->unsegmentForDiff( $formatter->format( $diffs ) );
- $difftext .= $this->debug( 'native PHP' );
-
- return $difftext;
+ $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ ->getSlotDiffRenderer( $this->getContext() );
+ if ( !( $slotDiffRenderer instanceof TextSlotDiffRenderer ) ) {
+ // Someone used the GetSlotDiffRenderer hook to replace the renderer.
+ // This is too unlikely to happen to bother handling properly.
+ throw new Exception( 'The slot diff renderer for text content should be a '
+ . 'TextSlotDiffRenderer subclass' );
+ }
+ return $slotDiffRenderer->getTextDiff( $otext, $ntext ) . $this->getDebugString();
}
/**
" -->\n";
}
+ private function getDebugString() {
+ $engine = self::getEngine();
+ if ( $engine === 'wikidiff2' ) {
+ return $this->debug( 'wikidiff2' );
+ } elseif ( $engine === false ) {
+ return $this->debug( 'native PHP' );
+ } else {
+ return $this->debug( "external $engine" );
+ }
+ }
+
/**
* Localise diff output
*
* @return string
*/
public function getMultiNotice() {
- if ( !is_object( $this->mOldRev ) || !is_object( $this->mNewRev ) ) {
- return '';
- } elseif ( !$this->mOldPage->equals( $this->mNewPage ) ) {
- // Comparing two different pages? Count would be meaningless.
+ // The notice only make sense if we are diffing two saved revisions of the same page.
+ if (
+ !$this->mOldRev || !$this->mNewRev
+ || !$this->mOldPage || !$this->mNewPage
+ || !$this->mOldPage->equals( $this->mNewPage )
+ ) {
return '';
}
* @param Content $oldContent
* @param Content $newContent
* @since 1.21
+ * @deprecated since 1.32, use setRevisions or ContentHandler::getSlotDiffRenderer.
*/
public function setContent( Content $oldContent, Content $newContent ) {
$this->mOldContent = $oldContent;
$this->mTextLoaded = 2;
$this->mRevisionsLoaded = true;
$this->isContentOverridden = true;
+ $this->slotDiffRenderers = null;
+ }
+
+ /**
+ * Use specified text instead of loading from the database.
+ * @param RevisionRecord|null $oldRevision
+ * @param RevisionRecord $newRevision
+ */
+ public function setRevisions(
+ RevisionRecord $oldRevision = null, RevisionRecord $newRevision
+ ) {
+ if ( $oldRevision ) {
+ $this->mOldRev = new Revision( $oldRevision );
+ $this->mOldid = $oldRevision->getId();
+ $this->mOldPage = Title::newFromLinkTarget( $oldRevision->getPageAsLinkTarget() );
+ // This method is meant for edit diffs and such so there is no reason to provide a
+ // revision that's not readable to the user, but check it just in case.
+ $this->mOldContent = $oldRevision ? $oldRevision->getContent( 'main',
+ RevisionRecord::FOR_THIS_USER, $this->getUser() ) : null;
+ } else {
+ $this->mOldRev = $this->mOldid = $this->mOldPage = null;
+ }
+ $this->mNewRev = new Revision( $newRevision );
+ $this->mNewid = $newRevision->getId();
+ $this->mNewPage = Title::newFromLinkTarget( $newRevision->getPageAsLinkTarget() );
+ $this->mNewContent = $newRevision->getContent( 'main',
+ RevisionRecord::FOR_THIS_USER, $this->getUser() );
+
+ $this->mRevisionsIdsLoaded = $this->mRevisionsLoaded = true;
+ $this->mTextLoaded = !!$oldRevision + 1;
+ $this->isContentOverridden = false;
+ $this->slotDiffRenderers = null;
}
/**
// Update the new revision ID in case it was 0 (makes life easier doing UI stuff)
$this->mNewid = $this->mNewRev->getId();
- $this->mNewPage = $this->mNewRev->getTitle();
+ if ( $this->mNewid ) {
+ $this->mNewPage = $this->mNewRev->getTitle();
+ } else {
+ $this->mNewPage = null;
+ }
// Load the old revision object
$this->mOldRev = false;
return false;
}
- if ( $this->mOldRev ) {
+ if ( $this->mOldRev && $this->mOldRev->getId() ) {
$this->mOldPage = $this->mOldRev->getTitle();
+ } else {
+ $this->mOldPage = null;
}
// Load tags information for both revisions
--- /dev/null
+<?php
+/**
+ * Adapter for turning a DifferenceEngine into a SlotDiffRenderer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * B/C adapter for turning a DifferenceEngine into a SlotDiffRenderer.
+ * Before SlotDiffRenderer was introduced, getDiff() functionality was provided by DifferenceEngine
+ * subclasses. Convert such a subclass into a SlotDiffRenderer.
+ * @deprecated
+ * @ingroup DifferenceEngine
+ */
+class DifferenceEngineSlotDiffRenderer extends SlotDiffRenderer {
+
+ /** @var DifferenceEngine */
+ private $differenceEngine;
+
+ public function __construct( DifferenceEngine $differenceEngine ) {
+ $this->differenceEngine = clone $differenceEngine;
+
+ // Set state to loaded. This should not matter to any of the methods invoked by
+ // the adapter, but just in case a load does get triggered somehow, make sure it's a no-op.
+ $fakeContent = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT )->makeEmptyContent();
+ $this->differenceEngine->setContent( $fakeContent, $fakeContent );
+
+ $this->differenceEngine->markAsSlotDiffRenderer();
+ }
+
+ /** @inheritDoc */
+ public function getDiff( Content $oldContent = null, Content $newContent = null ) {
+ if ( !$oldContent && !$newContent ) {
+ throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' );
+ }
+ if ( !$oldContent || !$newContent ) {
+ $someContent = $newContent ?: $oldContent;
+ $emptyContent = $someContent->getContentHandler()->makeEmptyContent();
+ $oldContent = $oldContent ?: $emptyContent;
+ $newContent = $newContent ?: $emptyContent;
+ }
+ return $this->differenceEngine->generateContentDiffBody( $oldContent, $newContent );
+ }
+
+ public function addModules( OutputPage $output ) {
+ $oldContext = null;
+ if ( $output !== $this->differenceEngine->getOutput() ) {
+ $oldContext = $this->differenceEngine->getContext();
+ $newContext = new DerivativeContext( $oldContext );
+ $newContext->setOutput( $output );
+ $this->differenceEngine->setContext( $newContext );
+ }
+ $this->differenceEngine->showDiffStyle();
+ if ( $oldContext ) {
+ $this->differenceEngine->setContext( $oldContext );
+ }
+ }
+
+ public function getExtraCacheKeys() {
+ return $this->differenceEngine->getExtraCacheKeys();
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Renders a diff for a single slot.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+/**
+ * Renders a diff for a single slot (that is, a diff between two content objects).
+ *
+ * Callers should obtain this class by invoking ContentHandler::getSlotDiffRendererClass
+ * on the content handler of the new content object (ie. the one shown on the right side
+ * of the diff), or of the old one if the new one does not exist.
+ *
+ * The default implementation just does a text diff on the native text representation.
+ * Content handler extensions can subclass this to provide a more appropriate diff method by
+ * overriding ContentHandler::getSlotDiffRendererClass. Other extensions that want to interfere
+ * with diff generation in some way can use the GetSlotDiffRenderer hook.
+ *
+ * @ingroup DifferenceEngine
+ */
+abstract class SlotDiffRenderer {
+
+ /**
+ * Get a diff between two content objects. One of them might be null (meaning a slot was
+ * created or removed), but both cannot be. $newContent (or if it's null then $oldContent)
+ * must have the same content model that was used to obtain this diff renderer.
+ * @param Content|null $oldContent
+ * @param Content|null $newContent
+ * @return string
+ */
+ abstract public function getDiff( Content $oldContent = null, Content $newContent = null );
+
+ /**
+ * Add modules needed for correct styling/behavior of the diff.
+ * @param OutputPage $output
+ */
+ public function addModules( OutputPage $output ) {
+ }
+
+ /**
+ * Return any extra keys to split the diff cache by.
+ * @return array
+ */
+ public function getExtraCacheKeys() {
+ return [];
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Renders a slot diff by doing a text diff on the native representation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup DifferenceEngine
+ */
+
+use MediaWiki\Shell\Shell;
+use Wikimedia\Assert\Assert;
+
+/**
+ * Renders a slot diff by doing a text diff on the native representation.
+ *
+ * If you want to use this without content objects (to call getTextDiff() on some
+ * non-content-related texts), obtain an instance with
+ * ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ * ->getSlotDiffRenderer( RequestContext::getMain() )
+ *
+ * @ingroup DifferenceEngine
+ */
+class TextSlotDiffRenderer extends SlotDiffRenderer {
+
+ /** Use the PHP diff implementation (DiffEngine). */
+ const ENGINE_PHP = 'php';
+
+ /** Use the wikidiff2 PHP module. */
+ const ENGINE_WIKIDIFF2 = 'wikidiff2';
+
+ /** Use an external executable. */
+ const ENGINE_EXTERNAL = 'external';
+
+ /** @var IBufferingStatsdDataFactory|null */
+ private $statsdDataFactory;
+
+ /** @var Language|null The language this content is in. */
+ private $language;
+
+ /**
+ * Number of paragraph moves the algorithm should attempt to detect.
+ * Only used with the wikidiff2 engine.
+ * @var int
+ * @see $wgWikiDiff2MovedParagraphDetectionCutoff
+ */
+ private $wikiDiff2MovedParagraphDetectionCutoff = 0;
+
+ /** @var string One of the ENGINE_* constants. */
+ private $engine = self::ENGINE_PHP;
+
+ /** @var string Path to an executable to be used as the diff engine. */
+ private $externalEngine;
+
+ /**
+ * Convenience helper to use getTextDiff without an instance.
+ * @param string $oldText
+ * @param string $newText
+ * @return string
+ */
+ public static function diff( $oldText, $newText ) {
+ /** @var $slotDiffRenderer TextSlotDiffRenderer */
+ $slotDiffRenderer = ContentHandler::getForModelID( CONTENT_MODEL_TEXT )
+ ->getSlotDiffRenderer( RequestContext::getMain() );
+ return $slotDiffRenderer->getTextDiff( $oldText, $newText );
+ }
+
+ public function setStatsdDataFactory( IBufferingStatsdDataFactory $statsdDataFactory ) {
+ $this->statsdDataFactory = $statsdDataFactory;
+ }
+
+ public function setLanguage( Language $language ) {
+ $this->language = $language;
+ }
+ /**
+ * @param int $cutoff
+ * @see $wgWikiDiff2MovedParagraphDetectionCutoff
+ */
+ public function setWikiDiff2MovedParagraphDetectionCutoff( $cutoff ) {
+ Assert::parameterType( 'integer', $cutoff, '$cutoff' );
+ $this->wikiDiff2MovedParagraphDetectionCutoff = $cutoff;
+ }
+
+ /**
+ * Set which diff engine to use.
+ * @param string $type One of the ENGINE_* constants.
+ * @param string|null $executable Path to an external exectable, only when type is ENGINE_EXTERNAL.
+ */
+ public function setEngine( $type, $executable = null ) {
+ $engines = [ self::ENGINE_PHP, self::ENGINE_WIKIDIFF2, self::ENGINE_EXTERNAL ];
+ Assert::parameter( in_array( $type, $engines, true ), '$type',
+ 'must be one of the TextSlotDiffRenderer::ENGINE_* constants' );
+ if ( $type === self::ENGINE_EXTERNAL ) {
+ Assert::parameter( is_string( $executable ) && is_executable( $executable ), '$executable',
+ 'must be a path to a valid executable' );
+ } else {
+ Assert::parameter( is_null( $executable ), '$executable',
+ 'must not be set unless $type is ENGINE_EXTERNAL' );
+ }
+ $this->engine = $type;
+ $this->externalEngine = $executable;
+ }
+
+ /** @inheritDoc */
+ public function getDiff( Content $oldContent = null, Content $newContent = null ) {
+ if ( !$oldContent && !$newContent ) {
+ throw new InvalidArgumentException( '$oldContent and $newContent cannot both be null' );
+ } elseif ( $oldContent && !( $oldContent instanceof TextContent ) ) {
+ throw new InvalidArgumentException( __CLASS__ . ' does not handle ' . get_class( $oldContent ) );
+ } elseif ( $newContent && !( $newContent instanceof TextContent ) ) {
+ throw new InvalidArgumentException( __CLASS__ . ' does not handle ' . get_class( $newContent ) );
+ }
+
+ if ( !$oldContent ) {
+ $oldContent = $newContent->getContentHandler()->makeEmptyContent();
+ } elseif ( !$newContent ) {
+ $newContent = $oldContent->getContentHandler()->makeEmptyContent();
+ }
+
+ $oldText = $oldContent->serialize();
+ $newText = $newContent->serialize();
+
+ return $this->getTextDiff( $oldText, $newText );
+ }
+
+ /**
+ * Diff the text representations of two content objects (or just two pieces of text in general).
+ * @param string $oldText
+ * @param string $newText
+ * @return string
+ */
+ public function getTextDiff( $oldText, $newText ) {
+ Assert::parameterType( 'string', $oldText, '$oldText' );
+ Assert::parameterType( 'string', $newText, '$newText' );
+
+ $diff = function () use ( $oldText, $newText ) {
+ $time = microtime( true );
+
+ $result = $this->getTextDiffInternal( $oldText, $newText );
+
+ $time = intval( ( microtime( true ) - $time ) * 1000 );
+ if ( $this->statsdDataFactory ) {
+ $this->statsdDataFactory->timing( 'diff_time', $time );
+ }
+
+ // TODO reimplement this using T142313
+ /*
+ // Log requests slower than 99th percentile
+ if ( $time > 100 && $this->mOldPage && $this->mNewPage ) {
+ wfDebugLog( 'diff',
+ "$time ms diff: {$this->mOldid} -> {$this->mNewid} {$this->mNewPage}" );
+ }
+ */
+
+ return $result;
+ };
+
+ /**
+ * @param Status $status
+ * @throws FatalError
+ */
+ $error = function ( $status ) {
+ throw new FatalError( $status->getWikiText() );
+ };
+
+ // Use PoolCounter if the diff looks like it can be expensive
+ if ( strlen( $oldText ) + strlen( $newText ) > 20000 ) {
+ $work = new PoolCounterWorkViaCallback( 'diff',
+ md5( $oldText ) . md5( $newText ),
+ [ 'doWork' => $diff, 'error' => $error ]
+ );
+ return $work->execute();
+ }
+
+ return $diff();
+ }
+
+ /**
+ * Diff the text representations of two content objects (or just two pieces of text in general).
+ * This does the actual diffing, getTextDiff() wraps it with logging and resource limiting.
+ * @param string $oldText
+ * @param string $newText
+ * @return string
+ * @throws Exception
+ */
+ protected function getTextDiffInternal( $oldText, $newText ) {
+ // TODO move most of this into three parallel implementations of a text diff generator
+ // class, choose which one to use via dependecy injection
+
+ $oldText = str_replace( "\r\n", "\n", $oldText );
+ $newText = str_replace( "\r\n", "\n", $newText );
+
+ // Better external diff engine, the 2 may some day be dropped
+ // This one does the escaping and segmenting itself
+ if ( $this->engine === self::ENGINE_WIKIDIFF2 ) {
+ $wikidiff2Version = phpversion( 'wikidiff2' );
+ if (
+ $wikidiff2Version !== false &&
+ version_compare( $wikidiff2Version, '1.5.0', '>=' )
+ ) {
+ $text = wikidiff2_do_diff(
+ $oldText,
+ $newText,
+ 2,
+ $this->wikiDiff2MovedParagraphDetectionCutoff
+ );
+ } else {
+ // Don't pass the 4th parameter for compatibility with older versions of wikidiff2
+ $text = wikidiff2_do_diff(
+ $oldText,
+ $newText,
+ 2
+ );
+
+ // Log a warning in case the configuration value is set to not silently ignore it
+ if ( $this->wikiDiff2MovedParagraphDetectionCutoff > 0 ) {
+ wfLogWarning( '$wgWikiDiff2MovedParagraphDetectionCutoff is set but has no
+ effect since the used version of WikiDiff2 does not support it.' );
+ }
+ }
+
+ return $text;
+ } elseif ( $this->engine === self::ENGINE_EXTERNAL ) {
+ # Diff via the shell
+ $tmpDir = wfTempDir();
+ $tempName1 = tempnam( $tmpDir, 'diff_' );
+ $tempName2 = tempnam( $tmpDir, 'diff_' );
+
+ $tempFile1 = fopen( $tempName1, "w" );
+ if ( !$tempFile1 ) {
+ return false;
+ }
+ $tempFile2 = fopen( $tempName2, "w" );
+ if ( !$tempFile2 ) {
+ return false;
+ }
+ fwrite( $tempFile1, $oldText );
+ fwrite( $tempFile2, $newText );
+ fclose( $tempFile1 );
+ fclose( $tempFile2 );
+ $cmd = [ $this->externalEngine, $tempName1, $tempName2 ];
+ $result = Shell::command( $cmd )
+ ->execute();
+ $exitCode = $result->getExitCode();
+ if ( $exitCode !== 0 ) {
+ throw new Exception( "External diff command returned code {$exitCode}. Stderr: "
+ . wfEscapeWikiText( $result->getStderr() )
+ );
+ }
+ $difftext = $result->getStdout();
+ unlink( $tempName1 );
+ unlink( $tempName2 );
+
+ return $difftext;
+ } elseif ( $this->engine === self::ENGINE_PHP ) {
+ if ( $this->language ) {
+ $oldText = $this->language->segmentForDiff( $oldText );
+ $newText = $this->language->segmentForDiff( $newText );
+ }
+ $ota = explode( "\n", $oldText );
+ $nta = explode( "\n", $newText );
+ $diffs = new Diff( $ota, $nta );
+ $formatter = new TableDiffFormatter();
+ $difftext = $formatter->format( $diffs );
+ if ( $this->language ) {
+ $difftext = $this->language->unsegmentForDiff( $difftext );
+ }
+
+ return $difftext;
+ }
+ throw new LogicException( 'Invalid engine: ' . $this->engine );
+ }
+
+}
return $this->__call( __FUNCTION__, func_get_args() );
}
- public function open( $server, $user, $password, $dbName ) {
- return $this->__call( __FUNCTION__, func_get_args() );
- }
-
public function fetchObject( $res ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
}
}
+ /**
+ * Open a new connection to the database (closing any existing one)
+ *
+ * @param string $server Database server host
+ * @param string $user Database user name
+ * @param string $password Database user password
+ * @param string $dbName Database name
+ * @return bool
+ * @throws DBConnectionError
+ */
+ abstract protected function open( $server, $user, $password, $dbName );
+
/**
* Construct a Database subclass instance given a database type and parameters
*
parent::__construct( $params );
}
- /**
- * Usually aborts on failure
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws DBConnectionError
- * @return bool|resource|null
- */
- public function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName ) {
# Test for driver support, to avoid suppressed fatal error
if ( !function_exists( 'sqlsrv_connect' ) ) {
throw new DBConnectionError(
$this->opened = true;
- return $this->conn;
+ return (bool)$this->conn;
}
/**
return 'mysql';
}
- /**
- * @param string $server
- * @param string $user
- * @param string $password
- * @param string $dbName
- * @throws Exception|DBConnectionError
- * @return bool
- */
- public function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName ) {
# Close/unset connection handle
$this->close();
return false;
}
- public function open( $server, $user, $password, $dbName ) {
+ protected function open( $server, $user, $password, $dbName ) {
# Test for Postgres support, to avoid suppressed fatal error
if ( !function_exists( 'pg_connect' ) ) {
throw new DBConnectionError(
return false;
}
- /** Open an SQLite database and return a resource handle to it
- * NOTE: only $dbName is used, the other parameters are irrelevant for SQLite databases
- *
- * @param string $server
- * @param string $user Unused
- * @param string $pass
- * @param string $dbName
- *
- * @throws DBConnectionError
- * @return bool
- */
- function open( $server, $user, $pass, $dbName ) {
+ protected function open( $server, $user, $pass, $dbName ) {
$this->close();
$fileName = self::generateFileName( $this->dbDir, $dbName );
if ( !is_readable( $fileName ) ) {
$this->conn = false;
throw new DBConnectionError( $this, "SQLite database not accessible" );
}
+ // Only $dbName is used, the other parameters are irrelevant for SQLite databases
$this->openFile( $fileName, $dbName );
return (bool)$this->conn;
*/
public function getType();
- /**
- * Open a new connection to the database (closing any existing one)
- *
- * @param string $server Database server host
- * @param string $user Database user name
- * @param string $password Database user password
- * @param string $dbName Database name
- * @return bool
- * @throws DBConnectionError
- */
- public function open( $server, $user, $password, $dbName );
-
/**
* Fetch the next row from the given result object, in object form.
* Fields can be retrieved with $row->fieldname, with fields acting like
throw new MWException( __METHOD__ . ": skip function file not found: \"$localPath\"" );
}
$contents = $this->stripBom( file_get_contents( $localPath ) );
- if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
- $contents = $this->validateScriptFile( $localPath, $contents );
- }
return $contents;
}
throw new MWException( __METHOD__ . ": script file not found: \"$localPath\"" );
}
$contents = $this->stripBom( file_get_contents( $localPath ) );
- if ( $this->getConfig()->get( 'ResourceLoaderValidateStaticJS' ) ) {
- // Static files don't really need to be checked as often; unlike
- // on-wiki module they shouldn't change unexpectedly without
- // admin interference.
- $contents = $this->validateScriptFile( $fileName, $contents );
- }
$js .= $contents . "\n";
}
return $js;
* @ingroup Search
*/
abstract class SearchEngine {
+ const DEFAULT_SORT = 'relevance';
+
/** @var string */
public $prefix = '';
/** @var bool */
protected $showSuggestion = true;
- private $sort = 'relevance';
+ private $sort = self::DEFAULT_SORT;
/** @var array Feature values */
protected $features = [];
/**
* Get the valid sort directions. All search engines support 'relevance' but others
- * might support more. The default in all implementations should be 'relevance.'
+ * might support more. The default in all implementations must be 'relevance.'
*
* @since 1.25
* @return string[] the valid sort directions for setSort
*/
public function getValidSorts() {
- return [ 'relevance' ];
+ return [ self::DEFAULT_SORT ];
}
/**
$uTalkTitle,
$this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
[],
- [ 'redirect' => 'no' ]
+ $uTalkTitle->isRedirect() ? [ 'redirect' => 'no' ] : []
);
$newMessagesDiffLink = Linker::linkKnown(
[
'label' => $this->msg( 'movetalk' )->text(),
'help' => new OOUI\HtmlSnippet( $this->msg( 'movepagetalktext' )->parseAsBlock() ),
+ 'helpInline' => true,
'align' => 'inline',
- 'infusable' => true,
'id' => 'wpMovetalk-field',
]
);
*/
protected $fulltext;
+ /**
+ * @var string
+ */
+ protected $sort;
+
/**
* @var bool
*/
$this->setExtraParam( 'prefix', $this->mPrefix );
}
+ $this->sort = $request->getVal( 'sort', SearchEngine::DEFAULT_SORT );
+ if ( $this->sort !== SearchEngine::DEFAULT_SORT ) {
+ $this->setExtraParam( 'sort', $this->sort );
+ }
+
$user = $this->getUser();
# Extract manually requested namespaces
$search->setFeatureData( 'rewrite', $this->runSuggestion );
$search->setLimitOffset( $this->limit, $this->offset );
$search->setNamespaces( $this->namespaces );
+ $search->setSort( $this->sort );
$search->prefix = $this->mPrefix;
Hooks::run( 'SpecialSearchSetupEngine', [ $this, $this->profile, $search ] );
* @param string $text
*
* @throws InvalidArgumentException If the namespace is invalid
- * @return string
+ * @return string Namespace name with underscores (not spaces)
*/
public function getNamespaceName( $namespace, $text ) {
if ( $this->language->needsGenderDistinction() &&
* @return string
*/
public function formatTitle( $namespace, $text, $fragment = '', $interwiki = '' ) {
- if ( $namespace !== 0 && $namespace !== false ) {
- // Try to get a namespace name, but fallback
- // to empty string if it doesn't exist. And
- // assume that ns 0 is the empty string.
+ $out = '';
+ if ( $interwiki !== '' ) {
+ $out = $interwiki . ':';
+ }
+
+ if ( $namespace != 0 ) {
try {
$nsName = $this->getNamespaceName( $namespace, $text );
} catch ( InvalidArgumentException $e ) {
- $nsName = '';
+ // See T165149. Awkward, but better than erroneously linking to the main namespace.
+ $nsName = $this->language->getNsText( NS_SPECIAL ) . ":Badtitle/NS{$namespace}";
}
- $text = $nsName . ':' . $text;
- }
- if ( $fragment !== '' ) {
- $text = $text . '#' . $fragment;
+ $out .= $nsName . ':';
}
+ $out .= $text;
- if ( $interwiki !== '' ) {
- $text = $interwiki . ':' . $text;
+ if ( $fragment !== '' ) {
+ $out .= '#' . $fragment;
}
- $text = str_replace( '_', ' ', $text );
+ $out = str_replace( '_', ' ', $out );
- return $text;
+ return $out;
}
/**
* @return string
*/
public function getPrefixedText( LinkTarget $title ) {
- return $this->formatTitle(
- $title->getNamespace(),
- $title->getText(),
- '',
- $title->getInterwiki()
- );
+ if ( !isset( $title->prefixedText ) ) {
+ $title->prefixedText = $this->formatTitle(
+ $title->getNamespace(),
+ $title->getText(),
+ '',
+ $title->getInterwiki()
+ );
+ }
+
+ return $title->prefixedText;
}
/**
* @return string
*/
public function getPrefixedDBkey( LinkTarget $target ) {
- $key = '';
- if ( $target->isExternal() ) {
- $key .= $target->getInterwiki() . ':';
- }
- // Try to get a namespace name, but fallback
- // to empty string if it doesn't exist
- try {
- $nsName = $this->getNamespaceName(
- $target->getNamespace(),
- $target->getText()
- );
- } catch ( InvalidArgumentException $e ) {
- $nsName = '';
- }
-
- if ( $target->getNamespace() !== 0 ) {
- $key .= $nsName . ':';
- }
-
- $key .= $target->getText();
-
- return strtr( $key, ' ', '_' );
+ return strtr( $this->formatTitle(
+ $target->getNamespace(),
+ $target->getDBkey(),
+ '',
+ $target->getInterwiki()
+ ), ' ', '_' );
}
/**
*/
protected $interwiki;
+ /**
+ * Text form including namespace/interwiki, initialised on demand
+ *
+ * Only public to share cache with TitleFormatter
+ *
+ * @private
+ * @var string
+ */
+ public $prefixedText = null;
+
/**
* Constructs a TitleValue.
*
/**
* Reset internal cache for unit testing
+ * @codeCoverageIgnore
*/
public static function resetCache() {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
$iwResults = [];
foreach ( $resultSets as $resultSet ) {
- $result = $resultSet->next();
- while ( $result ) {
+ foreach ( $resultSet as $result ) {
if ( !$result->isBrokenTitle() ) {
$iwResults[$result->getTitle()->getInterwiki()][] = $result;
}
- $result = $resultSet->next();
}
}
'9只' => '9隻',
'9余' => '9餘',
'·范' => '·范',
+'’m' => '’m',
+'’re' => '’re',
'’s' => '’s',
+'’t' => '’t',
'、面点' => '、麵點',
'。个中' => '。箇中',
'〇周后' => '〇周後',
'债累累' => '債纍纍',
'傻里傻气' => '傻裡傻氣',
'仅余' => '僅餘',
+'像赞' => '像讚',
'仆人' => '僕人',
'仆使' => '僕使',
'仆仆' => '僕僕',
'劉佳怜' => '劉佳怜',
'刘芸后' => '劉芸后',
'劉芸后' => '劉芸后',
-'力拼' => '力拚',
-'力拼众敌' => '力拼眾敵',
'力争上游' => '力爭上遊',
'功勋' => '功勳',
'加氢精制' => '加氫精制',
'勾干' => '勾幹',
'勾心斗角' => '勾心鬥角',
'勾魂荡魄' => '勾魂蕩魄',
+'包干' => '包幹',
'包括' => '包括',
'包准' => '包準',
'包谷' => '包穀',
'寿面' => '壽麵',
'夏于乔' => '夏于喬',
'夏于喬' => '夏于喬',
+'夏后氏' => '夏后氏',
'夏历' => '夏曆',
'夏历史' => '夏歷史',
'夏游' => '夏遊',
'恋恋不舍' => '戀戀不捨',
'成于思' => '成於思',
'戬谷' => '戩穀',
-'截发' => '截髮',
'战天斗地' => '戰天鬥地',
'战栗' => '戰慄',
'战斗' => '戰鬥',
'打卡钟' => '打卡鐘',
'打吨' => '打吨',
'打干' => '打幹',
-'打拼' => '打拚',
'打断发' => '打斷發',
'打卤' => '打滷',
'打谷' => '打穀',
'拔须' => '拔鬚',
'拗别' => '拗彆',
'拙朴' => '拙樸',
-'拼却' => '拚卻',
-'拼命' => '拚命',
-'拼舍' => '拚捨',
-'拼死' => '拚死',
-'拼生尽死' => '拚生盡死',
-'拼绝' => '拚絕',
-'拼老命' => '拚老命',
-'拼斗' => '拚鬥',
+'拚舍' => '拚捨',
'拜托' => '拜託',
'括发' => '括髮',
'拭干' => '拭乾',
'拮据' => '拮据',
'拳局' => '拳跼',
-'æ\8b¼æ»æ\8b¼æ´»' => 'æ\8b¼æ»æ\8b¼æ´»',
+'æ\8b¼æ\96\97' => 'æ\8b¼é¬¥',
'拾沈' => '拾瀋',
'拿准' => '拿準',
'拿破仑' => '拿破崙',
'摄制' => '攝製',
'支干' => '支幹',
'支配欲' => '支配慾',
+'收回' => '收回',
'收获' => '收穫',
'改制成' => '改制成',
'改征' => '改徵',
'卤鸭' => '滷鴨',
'卤鹅' => '滷鵝',
'卤面' => '滷麵',
-'满拼自尽' => '滿拚自盡',
'满满当当' => '滿滿當當',
'满头洋发' => '滿頭洋髮',
'漂荡' => '漂蕩',
'火并非' => '火並非',
'火并' => '火併',
'火山里' => '火山裡',
-'火拼' => '火拚',
'火折子' => '火摺子',
'火签' => '火籤',
'灰蒙' => '灰濛',
'舌干唇焦' => '舌乾唇焦',
'舍入口' => '舍入口',
'舒卷' => '舒捲',
+'舞台风格' => '舞台風格',
'舞后' => '舞后',
'航海历' => '航海曆',
'航海历史' => '航海歷史',
'蟻后' => '蟻后',
'蚃干' => '蠁幹',
'蛮干' => '蠻幹',
-'血拼' => '血拚',
'血余' => '血餘',
'行事历' => '行事曆',
'行事历史' => '行事歷史',
'鉴赏' => '鑑賞',
'長几' => '長几',
'长几' => '長几',
+'长得丑' => '長得醜',
'长历' => '長曆',
'长历史' => '長歷史',
'长发公主' => '長髮公主',
'嗩' => '唢',
'嗶' => '哔',
'嗹' => '𪡏',
-'嘅' => '慨',
'嘆' => '叹',
'嘍' => '喽',
'嘑' => '呼',
'乾陵' => '乾陵',
'乾隆' => '乾隆',
'乾音' => '乾音',
+'乾顺' => '乾顺',
'乾顧' => '乾顾',
'乾顾' => '乾顾',
'乾風' => '乾风',
'山崎闇斋' => '山崎闇斋',
'山崎闇齋' => '山崎闇斋',
'岳託' => '岳讬',
+'峯會' => '峰会',
+'巔峯' => '巅峰',
'巨著' => '巨著',
'乾乾淨淨' => '干干净净',
'乾乾脆脆' => '干干脆脆',
'承乾' => '承乾',
'拉鍊' => '拉链',
'拙著' => '拙著',
-'拚命' => '拚命',
-'拚搏' => '拚搏',
-'拚死' => '拚死',
+'拚却' => '拚却',
+'拚卻' => '拚却',
+'拚弃' => '拚弃',
+'拚棄' => '拚弃',
+'拚生尽死' => '拚生尽死',
+'拚生盡死' => '拚生尽死',
'拾瀋' => '拾渖',
'挨剋' => '挨剋',
'提昇' => '提升',
'氾濫' => '泛滥',
'洗鍊' => '洗练',
'瀋液' => '渖液',
+'满拚自尽' => '满拚自尽',
+'滿拚自盡' => '满拚自尽',
'薰習' => '熏习',
'薰心' => '熏心',
'薰沐' => '熏沐',
'讎正' => '雠正',
'讎問' => '雠问',
'雪鍊' => '雪链',
+'頂峯' => '顶峰',
'項鍊' => '项链',
'飛昇' => '飞升',
'飭令' => '飭令',
'希拉莉' => '希拉蕊',
'希拉里' => '希拉蕊',
'希特拉' => '希特勒',
+'傷殘奧林匹克' => '帕拉林匹克',
+'残疾人奥林匹克' => '帕拉林匹克',
+'残奥会' => '帕運會',
+'殘奧會' => '帕運會',
'巴尔米拉环礁' => '帕邁拉環礁',
'帕劳' => '帛琉',
'希拉克' => '席哈克',
'劳拉' => '蘿拉',
'荧光' => '螢光',
'荧屏' => '螢屏',
-'屏幕' => '螢幕',
'行人路权' => '行人路權',
'行人路權' => '行人路權',
'流動作業系統' => '行動作業系統',
'馬里共和國' => '馬利共和國',
'马里共和国' => '馬利共和國',
'马拉维' => '馬拉威',
+'馬勒當拿' => '馬拉度納',
+'马拉多纳' => '馬拉度納',
'馬斯特里赫特' => '馬斯垂克',
'马斯特里赫特' => '馬斯垂克',
'马耳他' => '馬爾他',
'伴著者' => '伴著者',
'伴著述' => '伴著述',
'伴著錄' => '伴著錄',
+'服务器' => '伺服器',
'布下了' => '佈下了',
'布下的' => '佈下的',
'布光' => '佈光',
'侵占' => '侵佔',
'促著' => '促着',
'俄占' => '俄佔',
+'奧勒岡' => '俄勒岡',
'保障著' => '保障着',
'保障著作' => '保障著作',
'保障著名' => '保障著名',
'備著者' => '備著者',
'備著述' => '備著述',
'備著錄' => '備著錄',
+'帕拉林匹克' => '傷殘奧林匹克',
+'残疾人奥林匹克' => '傷殘奧林匹克',
'傻里傻气' => '傻裏傻氣',
'雇员' => '僱員',
'雇用' => '僱用',
'历史里' => '歷史裏',
'死里求生' => '死裏求生',
'死里逃生' => '死裏逃生',
+'帕運會' => '殘奧會',
'殺著' => '殺着',
'殺著作' => '殺著作',
'殺著名' => '殺著名',
'蘸著錄' => '蘸著錄',
'蜜里调油' => '蜜裏調油',
'荧屏' => '螢屏',
-'屏幕' => '螢幕',
'人行道' => '行人路',
'行家里手' => '行家裏手',
'首席执行官' => '行政總裁',
'糊口' => '餬口',
'馬里蘭' => '馬利蘭',
'马里兰' => '馬利蘭',
+'馬拉度納' => '馬勒當拿',
+'马拉多纳' => '馬勒當拿',
'马拉特·萨芬' => '馬拉特·沙芬',
'馬斯垂克' => '馬斯特里赫特',
'馬爾地夫' => '馬爾代夫',
'可攜式' => '便携式',
'攜帶型' => '便携式',
'促著' => '促着',
+'奧勒岡' => '俄勒冈',
'保護著' => '保护着',
'保鑣' => '保镖',
'保障著' => '保障着',
'尼日爾' => '尼日尔',
'區域網' => '局域网',
'區域網路' => '局域网络',
-'螢幕' => '屏幕',
'展著' => '展着',
'展著書' => '展著书',
'展著作' => '展著作',
'梵谷' => '梵高',
'欠帳' => '欠账',
'死帳' => '死账',
+'帕運會' => '残奥会',
+'傷殘奧林匹克' => '残疾人奥林匹克',
+'帕拉林匹克' => '残疾人奥林匹克',
'庇里牛斯' => '比利牛斯',
'披索' => '比索',
'畢卡索' => '毕加索',
'營運長,' => '首席运营官,',
'馬爾地夫' => '马尔代夫',
'萌島' => '马恩岛',
+'馬勒當拿' => '马拉多纳',
+'馬拉度納' => '马拉多纳',
'馬拉威' => '马拉维',
'馬斯垂克' => '马斯特里赫特',
'馬爾他' => '马耳他',
"logentry-managetags-create": "$1 {{GENDER:$2|Кхоьллина}} билгало «$4»",
"log-name-tag": "Билгалонийн тептар",
"rightsnone": "(яц)",
+ "rightslogentry-temporary-group": "$1 (ханна, $2 кхаччалца)",
"feedback-adding": "АгӀона хетарг тӀетохар...",
"feedback-back": "ЮхагӀо",
"feedback-bugornote": "Хьайн техникин халонах лаьцна яздан хӀума делахь, дехар до, [$1 хаам бе тхоьга].\nДацахь хьан йиш ю хӀокху атта кепаца «[$3 $2]» агӀонг коммент тӀетоха хьан декъашхочун цӀарца, кхин лелош йолу браузер билгал еш.",
"about": "Ăнлантарни",
"article": "Статья",
"newwindow": "(çĕнĕ чӳречере)",
- "cancel": "Пăрахăçла",
+ "cancel": "Çырмасăр хăвар",
"moredotdotdot": "Малалла…",
"mypage": "Страница",
"mytalk": "Сӳтсе явни",
"yourname": "Усă куракан ят:",
"userlogin-yourname": "Усă куракан ят",
"yourpassword": "Вăрттăн сăмах:",
+ "userlogin-yourpassword": "Пароль",
"yourpasswordagain": "Вăрттăн сăмах тепре çырăр:",
"yourdomainname": "Сирĕн доменă:",
"login": "Кĕрĕр",
"nosuchuser": "$1 ятлă хутшăнакан çук.\nÇырнă ята тепĕр хут тĕрĕслĕр, е аяларах вырнаçнă формăна усă курса çĕнĕ хутшăнакана регистрацилĕр.",
"nosuchusershort": "$1 ятлă хутшăнакан çук. Ятне епле çырнине тĕрĕслĕр.",
"nouserspecified": "Сирĕн хутшăнаканăн ятне каламалла.",
- "wrongpassword": "ÐÑ\81иÑ\80 кÄ\83Ñ\82аÑ\80Ñ\82нÄ\83 вÄ\83Ñ\80Ñ\82Ñ\82Ä\83н Ñ\81Ä\83маÑ\85 Ñ\82Ä\95Ñ\80Ä\95Ñ\81 маÑ\80. УÑ\80Ä\83Ñ\85Ñ\85ине кÄ\83Ñ\82аÑ\80Ñ\82Ä\83р.",
+ "wrongpassword": "ЯÑ\82Ä\83 е паÑ\80олÑ\8cÄ\95 Ñ\82Ä\95Ñ\80Ä\95Ñ\81 маÑ\80.\nТепÄ\95Ñ\80 Ñ\85Ñ\83Ñ\82 кÄ\95Ñ\80Ñ\82Ä\95р.",
"wrongpasswordempty": "Пушă мар пароль çырăр тархасшăн.",
"mailmypassword": "Çĕнĕ вăрттăн сăмаха ярса ил",
"passwordremindertitle": "{{grammar:genitive|{{SITENAME}}}} хутшăнаканăн вăрттăн сăмахне асаилтересси",
"accmailtext": "$1 вăрттăн сăмахне кунта леçрĕмĕр: $2.",
"newarticle": "(Çĕнни)",
"newarticletext": "Ссылка урлă эсир халлĕхе çук статья çине куçрăр.\nÇĕнĕ статьяна кĕртес тесен аяларах вырнаçнă чӳречере текста çырăр.\n(тĕплĕнрех пĕлес тесен [$1 пулăшу страниципе] паллашăр).\nЕнчен те эсир кунта йăнăшпа лекнĕ пулсан — сирĕн браузерăн <strong>Каялла</strong> кнопка çине пусăр.",
+ "userpage-userdoesnotexist-view": "\"$1\" аккаунтне туман.",
"usercsspreview": "'''Ан манăр, эсир сирĕн css файл епле пулассине çеç куратăр, ăна халлĕхе çырса хуман!'''",
"userjspreview": "'''Астăвăр, ку сирĕн javascript-файлăн малтанхи курăмĕ кăна, ăна хальлĕхе çырса хуман!'''",
"updated": "(Çĕнелнĕ)",
"templatesusedsection": "Ку пайĕнче усă куракан {{PLURAL:$1|шаблон|шаблонсем}}:",
"template-protected": "(сыхланă)",
"template-semiprotected": "(пĕр пайне сыхланă)",
+ "content-model-wikitext": "викитекст",
"expensive-parserfunction-category": "Кунта эсир чылай ресурс ыйтакан функцисемпе нумай ĕçлекен страницăсене куратăр",
"post-expand-template-argument-category": "Шаблон аргуменчĕсене сиктерсе хăварнă страницăсем",
"undo-norev": "Ку тӳрлетĕве пăрахăçлама май çук — вăл е пулман та, е ăна кăларса пăрахнă.",
"last": "малт.",
"page_first": "пĕрремĕш",
"page_last": "юлашки",
- "history-fieldset-title": "Ð\98Ñ\81Ñ\82оÑ\80ине пÄ\83Ñ\85",
+ "history-fieldset-title": "УлÄ\83Ñ\88Ä\83нниÑ\81ене Ñ\88Ñ\8bÑ\80а",
"histfirst": "киввисем",
"histlast": "çĕннисем",
"historysize": "({{PLURAL:$1|1 байт|$1 байт}})",
"recentchanges-submit": "Кăтарт",
"rcfilters-legend-heading": "<strong>Кĕскетнисем:</strong>",
"rcfilters-other-review-tools": "Урăх пăхмаллисем",
+ "rcfilters-activefilters-hide": "Кăтартмалла мар",
+ "rcfilters-activefilters-show": "Кăтартмалла",
"rcfilters-show-new-changes": "Çĕнĕ улăшăннисем",
"rcfilters-filterlist-title": "Фильтрсем",
"rcfilters-filter-editsbyself-label": "Хăвăр улăштарнисем",
"notargettitle": "Тĕллевне кăтартман",
"pager-newer-n": "{{PLURAL:$1|çĕнĕреххисене 1|$1 çĕнĕреххисене}}",
"pager-older-n": "{{PLURAL:$1|кивĕреххисене 1|$1 кивĕреххисене}}",
+ "apihelp-no-such-module": "\"$1\" модульĕ тупăнмарĕ.",
"booksources": "Кĕнекесен çăлкуçĕсем",
"booksources-search": "Туп",
- "specialloguserlabel": "Ð¥Ñ\83Ñ\82Ñ\88Ä\83накан:",
+ "specialloguserlabel": "ТÄ\83вакан:",
"log": "Логсем",
"logeventslist-submit": "Кăтарт",
"all-logs-page": "Пĕтĕм логсем",
"unwatch": "ан сăна",
"unwatchthispage": "Сăнама пăрах",
"notanarticle": "Ку статья мар",
+ "watchlist-hide": "Кăтартмалла мар",
"watchlist-submit": "Кăтарт",
"watching": "Сăнамаллисем шутне хушасси…",
"unwatching": "Сăнав ят-йышĕнчен кăларса пăрахасси…",
"mycontris": "Хушни",
"anoncontribs": "Хушни",
"contribsub2": "{{GENDER:$3|$1}} валли ($2)",
+ "contributions-userdoesnotexist": "\"$1\" аккаунтне туман.",
"uctop": "(хальхи)",
"month": "Уйăхран (маларах та):",
"year": "Çултан (маларах та):",
"allmessages-filter-submit": "Куç",
"allmessages-filter-translate": "Куçар",
"thumbnail-more": "Пысăклатмалли",
- "filemissing": "Файл тупăнмарĕ",
+ "filemissing": "Файлĕ тупăнмарĕ",
"thumbnail_error": "Пĕчĕк ӳкерчĕке тăваймарăмăр: $1",
"thumbnail_invalid_params": "Пĕчĕк ӳкерчĕкĕн параметрĕ йăнăш",
"import": "Страницăсене импортласси",
"import-noarticle": "Импортламалли страница çук!",
"importlogpage": "Импорт журналĕ",
"tooltip-pt-userpage": "Сирĕн хутшăнакан страници",
- "tooltip-pt-mytalk": "Сирĕн канашлу страници",
+ "tooltip-pt-mytalk": "{{GENDER:|Сирĕн}} сӳтсе явакан страницу",
"tooltip-pt-anontalk": "IP адресне сӳтсе явни",
"tooltip-pt-preferences": "Сирĕн ĕнерлевсем",
"tooltip-pt-watchlist": "Эсир пăхакан страницисем",
"fileduplicatesearch": "Пĕр пек файлсен шыравĕ",
"fileduplicatesearch-filename": "Файл ячĕ:",
"fileduplicatesearch-submit": "Туп",
+ "fileduplicatesearch-noresults": "\"$1\" ятлă файл тупăнмарĕ.",
"specialpages": "Ятарлă страницăсем",
"specialpages-group-maintenance": "Техника обслуживанийĕн отчечĕсем",
"specialpages-group-other": "Ытти ятарлă страницăсем",
"tag-filter": "[[Special:Tags|Тегсен]] фильтрĕ:",
"tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|Тег|Тегсем}}]]: $2)",
"tags-title": "Тегсем",
+ "tags-tag": "Тегĕн ячĕ",
"compare-submit": "Танлаштар",
"htmlform-selectorother-other": "Урăххи",
"htmlform-no": "Çук",
"pagelang-select-lang": "Чĕлхе суйлăр",
"mediastatistics-header-audio": "Аудио",
"mediastatistics-header-video": "Видеосем",
- "special-characters-group-symbols": "Символсем"
+ "special-characters-group-symbols": "Символсем",
+ "authmanager-userdoesnotexist": "\"$1\" аккаунтне туман."
}
"grant-createaccount": "Oprette af konti",
"grant-createeditmovepage": "Oprette, redigere og flytte sider",
"grant-delete": "Slette sider, revisioner og logposter",
- "grant-editinterface": "Redigere MediaWiki-navnerummet og bruger/side JSON",
+ "grant-editinterface": "Redigere MediaWiki-navnerummet og JSON for hele webstedet og brugere",
"grant-editmycssjs": "Redigere din bruger-CSS/JSON/JavaScript",
"grant-editmyoptions": "Redigere dine brugerindstillinger",
"grant-editmywatchlist": "Redigere din overvågningsliste",
"Florian",
"Tiin",
"Rhirsch",
- "Metalhead64"
+ "Metalhead64",
+ "Vogone"
]
},
"tog-hideminor": "Kleine Änderungen in den „Letzten Änderungen“ ausblenden",
"tog-editsectiononrightclick": "Einzelne Abschnitte per Rechtsklick bearbeiten",
"tog-enotifrevealaddr": "Ihre E-Mail-Adresse in Benachrichtigungs-E-Mails anzeigen",
"cancel": "Abbrechen",
+ "search-ignored-headings": " #<!-- diese Zeile nicht verändern --> <pre>\n# Überschriften, die von der Suche ignoriert werden.\n# Diese Änderungen werden wirksam, sobald die Seite mit der Überschrift indexiert wurde.\n# Sie können die Seitenindexierung erzwingen, indem Sie einen Nulledit durchführen.\n# Syntax:\n# * Alles, was einer Raute („#“) bis zum Zeilenende folgt, ist ein Kommentar.\n# * Jede nicht-leere Zeile ist der exakte zu ignorierende Titel.\nEinzelnachweise\nWeblinks\nSiehe auch\n #</pre> <!-- diese Zeile nicht verändern -->",
"view-pool-error": "Entschuldigen Sie bitte, dass die Server im Moment überlastet sind.\nZu viele Benutzer versuchen, diese Seite zu besuchen.\nBitte warten Sie einige Minuten, bevor Sie es noch einmal versuchen.\n\n$1",
"generic-pool-error": "Entschuldigen Sie bitte, dass die Server im Moment überlastet sind.\nZu viele Benutzer wollen diese Ressource ansehen.\nBitte warten Sie einen Moment, bevor Sie sie erneut aufrufen.",
"badaccess-group0": "Sie haben nicht die erforderlichen Benutzerrechte für diese Aktion.",
"youhavenewmessagesmanyusers": "Sie haben $1 von vielen Benutzern ($2).",
"youhavenewmessagesmulti": "Sie haben neue Nachrichten: $1",
"confirmable-confirm": "Sind {{GENDER:$1|Sie}} sicher?",
+ "transaction-duration-limit-exceeded": "Um eine große Verzögerung in der Datenreplikation zu vermeiden, wurde diese Transaktion abgebrochen. Die Schreibdauer ($1) hat die Grenze von $2 Sekunden überschritten. Falls Sie viele Objekte auf einmal ändern, versuchen Sie stattdessen, die Änderungen auf mehrere Operationen aufzuteilen.",
"enterlockreason": "Bitte geben Sie einen Grund ein, warum die Datenbank gesperrt werden soll und eine Abschätzung über die Dauer der Sperrung",
"readonlytext": "Die Datenbank ist vorübergehend für Neueinträge und Änderungen gesperrt. Bitte versuchen Sie es später noch einmal.\n\nGrund der Sperrung: $1",
"missing-article": "Der Text von „$1“ $2 wurde nicht in der Datenbank gefunden.\n\nDie Seite ist möglicherweise gelöscht oder verschoben worden.\n\nFalls dies nicht der Fall ist, haben Sie eventuell einen Fehler in der Software gefunden. Bitte melden Sie dies einem [[Special:ListUsers/sysop|Administrator]] unter Nennung der URL.",
"actionthrottledtext": "Im Rahmen einer Anti-Spam-Maßnahme kann diese Aktion in einem kurzen Zeitabstand nur begrenzt oft ausgeführt werden. Diese Grenze haben Sie überschritten.\nBitte versuchen Sie es in ein paar Minuten erneut.",
- "viewsourcetext": "Sie können den Quelltext dieser Seite betrachten und kopieren:",
- "viewyourtext": "Sie können den Quelltext '''Ihrer Bearbeitung''' dieser Seite betrachten und kopieren:",
+ "viewsourcetext": "Sie können den Quelltext dieser Seite betrachten und kopieren.",
+ "viewyourtext": "Sie können den Quelltext <strong>Ihrer Bearbeitung</strong> dieser Seite betrachten und kopieren.",
"protectedinterface": "Diese Seite enthält Text für die Benutzeroberfläche der Software auf diesem Wiki und ist geschützt, um Missbrauch vorzubeugen.\nNutzen Sie bitte [https://translatewiki.net/ translatewiki.net], das Lokalisierungsprojekt von MediaWiki, um Übersetzungen für alle Wikis hinzuzufügen oder zu ändern.",
"editinginterface": "'''Warnung:''' Diese Seite enthält von der MediaWiki-Software genutzten Text.\nÄnderungen auf dieser Seite wirken sich auf die Benutzeroberfläche dieses Wikis aus.\nNutzen Sie bitte [https://translatewiki.net/ translatewiki.net], das Lokalisierungsprojekt von MediaWiki, um Übersetzungen für alle Wikis hinzuzufügen oder zu ändern.",
"translateinterface": "Um Übersetzungen für alle Wikis hinzuzufügen oder zu ändern, verwenden Sie bitte [//translatewiki.net/ translatewiki.net], das MediaWiki-Lokalisierungsprojekt.",
"namespaceprotected": "Sie haben nicht die erforderliche Berechtigung, um Seiten im Namensraum '''$1''' bearbeiten zu können.",
"customcssprotected": "Sie haben nicht die Berechtigung, diese CSS enthaltende Seite zu bearbeiten, da sie die persönlichen Einstellungen eines anderen Benutzers enthält.",
+ "customjsonprotected": "Sie haben nicht die Berechtigung, diese CSS enthaltende Seite zu bearbeiten, da sie die persönlichen Einstellungen eines anderen Benutzers enthält.",
"customjsprotected": "Sie haben nicht die Berechtigung, diese JavaScript enthaltende Seite zu bearbeiten, da es sich hierbei um die persönlichen Einstellungen eines anderen Benutzers handelt.",
+ "sitejsonprotected": "Sie haben nicht die Berechtigung, diese JSON-Seite zu bearbeiten, da sie alle Besucher betreffen könnte.",
"mycustomcssprotected": "Sie haben keine Berechtigung, diese CSS-Seite zu bearbeiten.",
"mycustomjsprotected": "Sie haben keine Berechtigung, diese JavaScript-Seite zu bearbeiten.",
"myprivateinfoprotected": "Sie haben keine Berechtigung, Ihre privaten Informationen zu bearbeiten.",
"logentry-delete-delete": "{{GENDER:$2|Ο|Η}} $1 διέγραψε τη σελίδα $3",
"logentry-delete-delete_redir": "{{GENDER:$2|Ο|Η}} $1 διέγραψε την ανακατεύθυνση $3 με αντικατάσταση",
"logentry-delete-restore": "{{GENDER:$1|Ο|Η}} $1 αποκατέστησε τη σελίδα $3 ($4)",
+ "logentry-delete-restore-nocount": "{{GENDER:$2|Ο|Η}} $1 αποκατέστησε τη σελίδα $3",
+ "restore-count-revisions": "{{PLURAL:$1|1 τροποποίηση|$1 τροποποιήσεις}}",
+ "restore-count-files": "{{PLURAL:$1|1 αρχείο|$1 αρχεία}}",
"logentry-delete-event": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ορατότητα {{PLURAL:$5|ενός καταγραφόμενου συμβάντος|$5 καταγραφόμενων συμβάντων}} στο $3: $4",
"logentry-delete-revision": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ορατότητα {{PLURAL:$5|μίας αναθεώρησης|$5 αναθεωρήσεων}} στη σελίδα $3: $4",
"logentry-delete-event-legacy": "{{GENDER:$2|Ο|Η}} $1 άλλαξε την ορατότητα των καταγραφόμενων συμβάντων στη σελίδα $3",
"subject-preview": "Aperçu du sujet :",
"previewerrortext": "Une erreur s’est produite lors de la tentative de prévisualisation de vos modifications.",
"blockedtitle": "L’utilisateur est bloqué.",
- "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité n’a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
- "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité n’a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+ "blockedtext": "<strong>Votre compte utilisateur ou votre adresse IP a été bloqué.</strong>\n\nLe blocage a été effectué par $1.\nLa raison invoquée est la suivante : <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7.\n\nVous pouvez contacter $1 ou un autre [[{{MediaWiki:Grouppage-sysop}}|administrateur]] pour en discuter.\nVous ne pouvez utiliser la fonction « {{int:emailuser}} » que si une adresse de courriel valide est spécifiée dans vos [[Special:Preferences|préférences]] et que si cette fonctionnalité ne vous a pas été bloquée.\nVotre adresse IP actuelle est $3 et votre identifiant de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
+ "autoblockedtext": "Votre adresse IP a été bloquée automatiquement car elle a été utilisée par un autre utilisateur, lui-même bloqué par $1.\nLa raison invoquée est :\n\n: <em>$2</em>.\n\n* Début du blocage : $8\n* Expiration du blocage : $6\n* Compte bloqué : $7\n\nVous pouvez contacter $1 ou l’un des autres [[{{MediaWiki:Grouppage-sysop}}|administrateurs]] pour discuter de ce blocage.\n\nNotez que vous ne pourrez utiliser la fonctionnalité « {{int:emailuser}} » que si vous avez une adresse de courriel validée dans vos [[Special:Preferences|préférences]] et que cette fonctionnalité ne vous a pas été désactivée.\n\nVotre adresse IP actuelle est $3, et le numéro de blocage est $5.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
"systemblockedtext": "Votre nom d'utilisateur ou votre adresse IP ont été bloqués automatiquement par MediaWiki.\nLa raison donnée est la suivante:\n\n: <em>$2</em>.\n\n* Le début du blocage: $8\n* Expiration du délai de blocage: $6\n* Elément concerné: $7\n\nVotre adresse IP actuelle est $3.\nVeuillez inclure tous les détails ci-dessus dans chacune des requêtes que vous ferez.",
"blockednoreason": "aucune raison donnée",
"whitelistedittext": "Vous devez vous $1 pour avoir la permission de modifier le contenu.",
"difference-missing-revision": "{{PLURAL:$2|Una versione|$2 versioni}} di questa differenza ($1) {{PLURAL:$2|non è stata trovata|non sono state trovate}}.\n\nQuesto si verifica solitamente seguendo un collegamento obsoleto di un diff a una pagina cancellata.\nI dettagli possono essere trovati nel [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} registro delle cancellazioni].",
"searchresults": "Risultati della ricerca",
"search-filter-title-prefix": "Ricerca effettuata solo nelle pagine con titolo che inizia con \"$1\"",
- "search-filter-title-prefix-reset": "cerca in tutte le pagine",
+ "search-filter-title-prefix-reset": "Cerca in tutte le pagine",
"searchresults-title": "Risultati della ricerca di \"$1\"",
"titlematches": "Corrispondenze nel titolo delle pagine",
"textmatches": "Corrispondenze nel testo delle pagine",
"filehist-filesize": "Dimensione del file",
"filehist-comment": "Commento",
"imagelinks": "Utilizzo del file",
- "linkstoimage": "{{PLURAL:$1|La seguente pagina contiene|Le seguenti $1 pagine contengono}} collegamenti a questo file:",
- "linkstoimage-more": "Più di $1 {{PLURAL:$1|pagina punta|pagine puntano}} a questo file.\nDi seguito sono elencate solo {{PLURAL:$1|la prima pagina che punta|le prime $1 pagine che puntano}} a questo file.\nÈ disponibile un [[Special:WhatLinksHere/$2|elenco completo]].",
- "nolinkstoimage": "Nessuna pagina contiene collegamenti al file.",
+ "linkstoimage": "{{PLURAL:$1|La seguente pagina usa|Le seguenti $1 pagine usano}} questo file:",
+ "linkstoimage-more": "Più di $1 {{PLURAL:$1|pagina usa|pagine usano}} questo file.\nDi seguito sono elencate solo {{PLURAL:$1|la prima pagina che usa|le prime $1 pagine che usano}} questo file.\nÈ disponibile un [[Special:WhatLinksHere/$2|elenco completo]].",
+ "nolinkstoimage": "Nessuna pagina utilizza questo file.",
"morelinkstoimage": "Visualizza [[Special:WhatLinksHere/$1|altri collegamenti]] a questo file.",
"linkstoimage-redirect": "$1 (reindirizzamento file) $2",
"duplicatesoffile": "{{PLURAL:$1|Il seguente file è un duplicato|I seguenti $1 file sono duplicati}} di questo file ([[Special:FileDuplicateSearch/$2|ulteriori dettagli]]):",
"permissionserrors": "Masalah idin",
"permissionserrorstext": "Panjengan ora kagungan idin kanggo nglakoni sing panjenengan gayuh amerga {{PLURAL:$1|alesan|alesan-alesan}} iki:",
"permissionserrorstext-withaction": "Panjenengan ora diidinaké $2 amarga {{PLURAL:$1|alasan|alasan}} ing ngisor iki:",
- "recreate-moveddeleted-warn": "'''Pènget: Panjenengan gawé manèh sawijining kaca sing wis tau dibusak.'''\n\nMangga digagas manèh apa pantes nerusaké nyunting kaca iki.\nIng ngisor iki kapacak log pambusakan lan pamindhahan saka kaca iki:",
+ "recreate-moveddeleted-warn": "<strong>Pélik: Panjenengan nggawé manèh kaca kang tau kabusak.</strong>\n\nPanjenengan kudu nglelimbang apa pantes nerusaké mbesut kaca iki.\nIng isor iki kapacak log pambusak lan pangalih saka kaca iki:",
"moveddeleted-notice": "Kaca iki wis dibusak.\nLog busak, reksa, lan alih bab kacané cumepak ing ngisor minangka rujukan.",
"log-fulllog": "Deleng cathetan wutuh",
"edit-hook-aborted": "Besutan diwurungaké déning cangkolan.\nOra ana katerangané.",
"showingresults": "Ing ngisor iki dituduhaké {{PLURAL:$1|'''1''' kasil|'''$1''' kasil}}, wiwitané saking #<strong>$2</strong>.",
"showingresultsinrange": "Nuduhaké nganti {{PLURAL:$1|<strong>1</strong> kasil|<strong>$1</strong> kasil}} sajeroning penthangan #<strong>$2</strong> tekan #<strong>$3</strong>.",
"search-showingresults": "{{PLURAL:$4|Asil <strong>$1</strong> saka <strong>$3</strong>|Asil <strong>$1 – $2</strong> saka <strong>$3</strong>}}",
- "search-nonefound": "Ora ana kasil sing mathuk karo pitakoné.",
+ "search-nonefound": "Ora ana asil kang mathuk kuwèri.",
"search-nonefound-thiswiki": "Ora ana kasil sing jumbuh karo panjalukan ing situs iki.",
"powersearch-legend": "Panggolèkan sabanjuré (''advance search'')",
"powersearch-ns": "Golèk ing mandala aran:",
"rc-enhanced-hide": "Dhelikaké princèn",
"rc-old-title": "kawitané digawé minangka \"$1\"",
"recentchangeslinked": "Owahan magepokan",
- "recentchangeslinked-feed": "Owah-owahan sing gegayutan",
+ "recentchangeslinked-feed": "Owah-owahan kang magepokan",
"recentchangeslinked-toolbox": "Owahan magepokan",
"recentchangeslinked-title": "Owah-owahan kang magepokan \"$1\"",
"recentchangeslinked-summary": "Iki pratélaning owah-owahan sing mentas digawé tumrap ing kaca-kaca sing nggayut sawijining kaca (utawa kaca-kaca anggotaning sawijining kategori).\nKaca ing [[Special:Watchlist|pawawangané panjenegan]] <strong>dikandeli</strong>.",
"logeventslist-submit": "Tuduhaké",
"all-logs-page": "Kabèh log umum",
"alllogstext": "Pitontonan gabungan log-log sing ana ing {{SITENAME}}.\nPanjenengan bisa nyiyutaké sesawangané kanthi milih sawijining jinis log, jeneng panganggo (sènsitif-case), utawa kaca sing gegayutan (uga sènsitif-case).",
- "logempty": "Ora ditemokaké èntri log sing pas.",
+ "logempty": "Ora tinemu wiji kang cocog ing log",
"log-title-wildcard": "Golèk sesirah sing diwiwiti tulisan iki",
"showhideselectedlogentries": "Owah pakatonané èntri log sing dipilih",
"log-edit-tags": "Besut tag saka isian log sing dipilih",
"pageinfo-robot-noindex": "Ora éntuk",
"pageinfo-watchers": "Cacahing sing ngawasi kaca",
"pageinfo-visiting-watchers": "Cacahé pandeleng kaca sing nekani besutan anyar",
- "pageinfo-few-watchers": "{{PLURAL:$1|Sing niliki|Sing niliki}} kurang saka $1",
+ "pageinfo-few-watchers": "{{PLURAL:$1|Kang ndeleng|Kang ndeleng}} kurang saka $1",
"pageinfo-redirects-name": "Cacahing alihan menyang kaca iki",
"pageinfo-subpages-name": "Cacahing anak kaca saka kaca iki",
"pageinfo-subpages-value": "$1 ($2 {{PLURAL:$2|alihan|alihan}}; $3 {{PLURAL:$3|non-alihan|non-alihan}})",
- "pageinfo-firstuser": "Sing nggawé kaca",
+ "pageinfo-firstuser": "Kang nggawé kaca",
"pageinfo-firsttime": "Tanggal panggawéning kaca",
- "pageinfo-lastuser": "Sing mbesut kèri dhéwé",
+ "pageinfo-lastuser": "Kang wekasan mbesut",
"pageinfo-lasttime": "Tanggal besutan kèri dhéwé",
"pageinfo-edits": "Gunggunging besutan",
- "pageinfo-authors": "Gunggunging sing nganggit",
+ "pageinfo-authors": "Gunggung kang nganggit",
"pageinfo-recent-edits": "Cacahé besutan saiki (ing dalem $1 pungkasan)",
"pageinfo-recent-authors": "Cacahing sing nganggit dinané iki",
"pageinfo-magic-words": "{{PLURAL:$1|Tembung|Tembung}} mujarab ($1)",
"file-info": "ukuran barkas: $1, jinis MIME: $2",
"file-info-size": "$1 × $2 piksel, ukuran barkas: $3, jinis MIME: $4",
"file-info-size-pages": "$1 × $2 piksel, gedhéné berkas: $3, jinisé MIME: $4, $5 {{PLURAL:$5|kaca|kaca}}",
- "file-nohires": "Ora ana résolusi sing luwih dhuwur.",
+ "file-nohires": "Ora ana résolusi kang luwih dhuwur.",
"svg-long-desc": "Barkas SVG, nominal $1 × $2 piksel, gedhéning barkas: $3",
"svg-long-desc-animated": "Berkas SVG, nominal $1 × $2 piksel, gedhené berkas: $3",
"svg-long-error": "Berkas SVG ora sah: $1",
"fileduplicatesearch-filename": "ဖိုင်အမည်:",
"fileduplicatesearch-submit": "ရှာဖွေရန်",
"specialpages": "အထူး စာမျက်နှာများ",
+ "specialpages-note-top": "မှတ်ချက်",
"specialpages-note-restricted": "* ပုံမှန် အထူးစာမျက်နှာများ။\n* <span class=\"mw-specialpagerestricted\">ကန့်သတ်ထားသော အထူးစာမျက်နှာများ။</span>",
"specialpages-group-maintenance": "ထိန်းသိမ်းမှု အစီရင်ခံချက်များ",
"specialpages-group-other": "အခြားအထူးစာမျက်နှာများ",
"tog-watchlisthideminor": "Annascunne 'e cagnamiente piccerille d' 'a lista 'e cuntrollo mia",
"tog-watchlisthideliu": "Annascunne 'e cagnamiénte 'e l'utente riggistrate 'a l'elenco 'e cuntrollo",
"tog-watchlistreloadautomatically": "Recarreca 'a lista 'e paggene cuntrullate automaticamente quanno nu filtro se fosse cagnato (ce buò 'o JavaScript)",
+ "tog-watchlistunwatchlinks": "Azzecca marcature dirette ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) pe' secutà/nun secutà 'e cagnamente a 'e paggene (ce buò 'o JavaScript pe' puté ausà sta funziunalità).",
"tog-watchlisthideanons": "Annascunne 'e cagnamiente fatte d'anonime 'a l'elenco 'e cuntrollo",
"tog-watchlisthidepatrolled": "Annascunne 'e modifiche cuntrullate 'a l'elenco 'e cuntrollo",
"tog-watchlisthidecategorization": "Annascunne 'a categorizzazione d' 'e paggene",
"customcssprotected": "Nun v'è permesso 'a cagnà sta paggena CSS, pecché cuntene 'e mpustaziune perzunale 'e n'at'utente.",
"customjsonprotected": "Nun v'è permesso 'a cagnà sta paggena JSON, pecché cuntene 'e mpustaziune perzunale 'e n'at'utente.",
"customjsprotected": "Nun v'è permesso 'a cagnà sta paggena JavaScript, pecché cuntene 'e mpustaziune perzunale 'e n'at'utente.",
+ "sitecssprotected": "Nun téne premmesse pe' puté cagnà sta paggena CSS pecché putesse dà prubbleme a 'e vvisite",
+ "sitejsonprotected": "Nun téne premmesse pe' puté cagnà sta paggena JSON pecché putesse dà prubbleme a 'e vvisite",
"mycustomcssprotected": "Nun v'è permesso 'a cagnà sta paggena CSS.",
"mycustomjsonprotected": "Nun v'è permesso 'a cagnà sta paggena JSON.",
"mycustomjsprotected": "Nun v'è licenzia pe cagnà sta paggena JavaScript.",
"filehist-filesize": "Dimenziune d\"o file",
"filehist-comment": "Commento",
"imagelinks": "Jonte ê ffiure",
- "linkstoimage": "{{PLURAL:$1|Sta paggena cullega|$1 'e sti paggene cullegano}} a stu file:",
- "linkstoimage-more": "Cchiù 'e $1 {{PLURAL:$1|paggena cullega|paggene cullegano}} a stu file.<br />\nL'elenco ccà abbascio fà vedé {{PLURAL:$1|'a primma paggena ca cullega|'e primme $1 paggene ca cullegano}} sulamente a stu file.<br />\nNa [[Special:WhatLinksHere/$2|lista completa]] è a disposizione.",
- "nolinkstoimage": "Nisciuna paggena cullega a stu file.",
+ "linkstoimage": "{{PLURAL:$1|Pe sta paggena ce buò|Pe $1 'e sti paggene ce buò}} stu file:",
+ "linkstoimage-more": "Cchiù 'e $1 {{PLURAL:$1|paggena buò|paggene vonno}} stu file.<br />\nL'elenco ccà abbascio fà vedé {{PLURAL:$1|'a primma paggena ca buò|'e primme $1 paggene ca vonno}} sulamente a stu file.<br />\nNa [[Special:WhatLinksHere/$2|lista completa]] è a disposizione.",
+ "nolinkstoimage": "Pe' nisciuna paggena ce buò stu file.",
"morelinkstoimage": "Vide [[Special:WhatLinksHere/$1|cchiù cullegamiente]] a stu file.",
"linkstoimage-redirect": "$1 (redirezionamiente d' 'o file) $2",
"duplicatesoffile": "{{PLURAL:$1|'O file ccà abbascio è nu duplicato|'E $1 file ccà abbascio songo duplicate}} 'e stu file ([[Special:FileDuplicateSearch/$2|cchiù nfurmaziune]]):",
"sp-contributions-blocked-notice-anon": "St'IP è bloccato mò.\nL'urdemo elemento d' 'o riggistro 'e blocche è ripurtato ccà abbascio p'avé nu riferimento:",
"sp-contributions-search": "Ascìa 'e contribbute",
"sp-contributions-username": "Nnerizzo IP o nomme utente",
- "sp-contributions-toponly": "Facenno vedé sulamente 'e contribbute 'e l'urdeme verziune",
- "sp-contributions-newonly": "Facenno vedé sulamente 'e contribbute ca songo criazione 'e paggene",
+ "sp-contributions-toponly": "Sulamente 'e contribbute ca songo ll'urdeme verziune",
+ "sp-contributions-newonly": "Sulamente 'e contribbute ca songo criazione 'e paggene",
"sp-contributions-hideminor": "Annascunne cagnamiénte piccerille",
"sp-contributions-submit": "Truova",
"whatlinkshere": "Paggene ca cullegano a chesta",
"watchnologin": "Nie jesteś zalogowany",
"addwatch": "Dodaj do listy obserwowanych",
"addedwatchtext": "Strona „[[:$1|$1]]” wraz ze swoją stroną dyskusji została dodana do Twojej [[Special:Watchlist|listy obserwowanych]].",
- "addedwatchtext-talk": "Strona „[[:$1]]” i strony z nią związane zostały dodane do Twojej [[Special:Watchlist|listy obserwowanych]].",
+ "addedwatchtext-talk": "Strona „[[:$1]]” i strona z nią powiązana zostały dodane do Twojej [[Special:Watchlist|listy obserwowanych]].",
"addedwatchtext-short": "Strona „$1” została dodana do twojej listy obserwowanych.",
"removewatch": "Usuń z listy obserwowanych",
"removedwatchtext": "Strona „[[:$1|$1]]” wraz ze swoją stroną dyskusji została usunięta z Twojej [[Special:Watchlist|listy obserwowanych]].",
- "removedwatchtext-talk": "Strona „[[:$1]]” i strony z nią związane zostały usunięte z Twojej [[Special:Watchlist|listy obserwowanych]].",
+ "removedwatchtext-talk": "Strona „[[:$1]]” i strona z nią powiązana zostały usunięte z Twojej [[Special:Watchlist|listy obserwowanych]].",
"removedwatchtext-short": "Strona „$1” została usunięta z twojej listy obserwowanych.",
"watch": "Obserwuj",
"watchthispage": "Obserwuj",
"colon-separator": "{{optional}}\nChange it only if your language uses another character for ':' or it needs an extra space before the colon.",
"autocomment-prefix": "{{notranslate}}",
"pipe-separator": "{{optional}}",
- "word-separator": "{{optional}}\nThis is a string which is (usually) put between words of the language. It is used, e.g. when messages are concatenated (appended to each other). Note that you must express a space as html entity &#32; because the editing and updating process strips leading and trailing spaces from messages.\n\nMost languages use a space, but some Asian languages, such as Thai and Chinese, do not.",
+ "word-separator": "{{optional}}\nThis is a string which is (usually) put between words of the language. It is used, e.g. when messages are concatenated (appended to each other). Note that you must express a space as html entity &#32; because the editing and updating process strips leading and trailing spaces from messages.\n\nMost languages use a space, but some Asian languages, such as Thai and Chinese, do not.\n{{Format|plain}}",
"ellipsis": "{{optional}}",
"percent": "{{optional}}",
"parentheses": "{{optional}}",
- "brackets": "{{Optional}}",
+ "brackets": "{{Optional}}\n{{Format|plain}}",
"quotation-marks": "Quotation marks, for quoting, sometimes titles etc., depending on the language.\n\nSee: [[w:Non-English usage of quotation marks|Non-English usage of quotation marks on Wikipedia]].\n\nParameters:\n* $1 - text to be wrapped in quotation marks",
"imgmultipageprev": "{{Identical|Previous page}}",
"imgmultipagenext": "{{Identical|Next page}}",
"customcssprotected": "Nu aveți permisiunea de a modifica această pagină CSS, deoarece conține setările personale ale altui utilizator.",
"customjsonprotected": "Nu aveți permisiunea de a modifica această pagină JSON, deoarece conține setările personale ale altui utilizator.",
"customjsprotected": "Nu aveți permisiunea de a modifica această pagină JavaScript, deoarece conține setările personale ale altui utilizator.",
+ "sitecssprotected": "Nu aveți dreptul să editați această pagină CSS deoarece poate afecta toți vizitatorii",
+ "sitejsonprotected": "Nu aveți dreptul să editați această pagină JSON deoarece poate afecta toți vizitatorii",
+ "sitejsprotected": "Nu aveți dreptul să editați această pagină JavaScript deoarece poate afecta toți vizitatorii",
"mycustomcssprotected": "Nu aveți permisiunea să modificați această pagină CSS.",
"mycustomjsonprotected": "Nu aveți permisiunea să modificați această pagină JSON.",
"mycustomjsprotected": "Nu aveți permisiunea să modificați această pagină JavaScript.",
"blanknamespace": "(Сүрүн)",
"contributions": "{{GENDER:$1|Кыттааччы}} суруйуута (кылаата)",
"contributions-title": "$1 кыттааччы киллэрбит уларытыылара",
- "mycontris": "Суруйуу тиһигэ",
+ "mycontris": "Суруйуу испииһэгэ",
"anoncontribs": "Суруйуу тиһилигэ",
"contribsub2": "$1 ($2) суруйуута",
"contributions-userdoesnotexist": "Маннык \"$1\" кыттааччы аата бэлиэтэниллибэтэх.",
"sunday": "ⴰⵙⴰⵎⴰⵙ",
"monday": "ⴰⵢⵏⴰⵙ",
"tuesday": "ⴰⵙⵉⵏⴰⵙ",
- "wednesday": "Akras",
- "thursday": "ⴰⴽⵡⴰⵙ",
- "friday": "â´°âµ\99âµ\89âµ\8eⵡⴰâµ\99",
- "saturday": "asidyas",
- "sun": "asamas",
- "mon": "Aynas",
- "tue": "Asinas",
- "wed": "Akras",
- "thu": "Akwas",
- "fri": "Asimwas",
- "sat": "Asidyas",
+ "wednesday": "ⵍⴰⵔⴱⵄ",
+ "thursday": "âµ\8dâµ\85âµ\8eâµ\89ⵙ",
+ "friday": "âµ\8dâµ\8aâ´°âµ\8eâµ\84",
+ "saturday": "ⵙⵙⴱⵜ",
+ "sun": "ⵍⵃⴷⴷ",
+ "mon": "ⵍⵜⵏⵉⵏ",
+ "tue": "ⵟⵟⵍⴰⵜⴰ",
+ "wed": "ⵍⴰⵔⴱⵄ",
+ "thu": "ⵍⵅⵎⵉⵙ",
+ "fri": "ⵍⵊⴰⵎⵄ",
+ "sat": "ⵙⵙⴱⵜ",
"january": "ⵉⵏⵏⴰⵢⵔ",
"february": "ⴼⴱⵔⴰⵢⵔ",
"march": "ⵎⴰⵔⵚ",
"june": "ⵢⵓⵏⵢⵓ",
"july": "ⵢⵓⵍⵢⵓⵣ",
"august": "ⵖⵓⵛⵜ",
- "september": "âµ\9bâµ\93âµ\9câ´°âµ\8fⴱⵉⵔ",
+ "september": "âµ\9bâµ\93âµ\9câ´°âµ\8eⴱⵉⵔ",
"october": "ⴽⵜⵓⴱⵔ",
- "november": "âµ\8fâµ\93ⵡⴰâµ\8fⴱⵉⵔ",
- "december": "â´·âµ\93âµ\8aâ´°âµ\8fⴱⵉⵔ",
+ "november": "âµ\8fâµ\93ⵡⴰâµ\8eⴱⵉⵔ",
+ "december": "â´·âµ\93âµ\8aâ´°âµ\8eⴱⵉⵔ",
"january-gen": "ⵉⵏⵏⴰⵢⵔ",
"february-gen": "ⴼⴱⵔⴰⵢⵔ",
"march-gen": "ⵎⴰⵔⵚ",
"june-gen": "ⵢⵓⵏⵢⵓ",
"july-gen": "ⵢⵓⵍⵢⵓⵣ",
"august-gen": "ⵖⵓⵛⵜ",
- "september-gen": "âµ\9bâµ\93âµ\9câ´°âµ\8fⴱⵉⵔ",
+ "september-gen": "âµ\9bâµ\93âµ\9câ´°âµ\8eⴱⵉⵔ",
"october-gen": "ⴽⵜⵓⴱⵔ",
"november-gen": "ⵏⵓⵡⴰⵎⴱⵉⵔ",
- "december-gen": "â´·âµ\93âµ\8aâ´°âµ\8fⴱⵉⵔ",
+ "december-gen": "â´·âµ\93âµ\8aâ´°âµ\8eⴱⵉⵔ",
"jan": "ⵉⵏⵏ",
"feb": "brayr",
- "mar": "âµ\8eâ´°âµ\94",
+ "mar": "âµ\8eâ´°âµ\95",
"apr": "ⴰⴱⵔ",
"may": "ⵎⴰⵢ",
"jun": "ⵢⵓⵏ",
"june-date": "$1 ⵢⵓⵏⵢⵓ",
"july-date": "$1 ⵢⵓⵍⵢⵓⵣ",
"august-date": "$1 ⵖⵓⵛⵜ",
- "september-date": "$1 âµ\9bâµ\93âµ\9câ´°âµ\8fⴱⵉⵔ",
+ "september-date": "$1 âµ\9bâµ\93âµ\9câ´°âµ\8eⴱⵉⵔ",
"october-date": "$1 ⴽⵜⵓⴱⵔ",
- "november-date": "$1 âµ\8fâµ\93ⵡⴰâµ\8fⴱⵉⵔ",
- "december-date": "$1 â´·âµ\93âµ\8aâ´°âµ\8fⴱⵉⵔ",
+ "november-date": "$1 âµ\8fâµ\93ⵡⴰâµ\8eⴱⵉⵔ",
+ "december-date": "$1 â´·âµ\93âµ\8aâ´°âµ\8eⴱⵉⵔ",
"pagecategories": "{{PLURAL:$1|ⵜⴰⴳⴳⴰⵢⵜ|ⵜⴰⴳⴳⴰⵢⵉⵏ}}",
"category_header": "ⵜⴰⵙⵏⵉⵡⵉⵏ ⵏ ⵜⴰⴳⴳⴰⵢⵜ \"$1\"",
"subcategories": "ⵜⵉⴷⵓⴳⴳⴰⵢⵉⵏ",
"updatedmarker": "Tuybddal z tizrink li iğuran",
"printableversion": "ⴰⵎⴱⵔⵉⵎⵉ ⵜⴰⵙⵏⴰ ⴰⴷ",
"permalink": "Azday Bdda illan",
- "print": "Siggz",
+ "print": "ⴰⵎⴱⵔⵉⵎⵉ",
"edit": "ⵙⵏⴼⵍ",
"create": "ⵙⵏⵓⵍⴼⵓ",
"delete": "ⴽⴽⵙ",
"nosuchaction": "Nuk ekziston ky veprim",
"nosuchactiontext": "Veprimi i specifikuar nga URL është i pavlefshëm.\nJu mund të keni bërë një gabim në shkrimin e URL-së, ose keni ndjekur një lidhje të pasaktë.\nKjo mund të vijë edhe si rezultat i një gabimi në programin e përdorur nga {{SITENAME}}.",
"nosuchspecialpage": "Nuk ekziston kjo faqe speciale",
- "nospecialpagetext": "<strong>Ju keni kërkuar një faqe speciale të pavlefshme.</strong> \n\n Një listë e faqeve speciale të vlefshme mund të gjendet në [[Special:SpecialPages|{{int: specialpages }}]].",
+ "nospecialpagetext": "<strong>Ju keni kërkuar një faqe speciale të pavlefshme.</strong> \n\nNjë listë e faqeve speciale të vlefshme mund të gjendet në [[Special:SpecialPages|{{int: specialpages }}]].",
"error": "Gabim",
"databaseerror": "Gabim në databazë",
"databaseerror-text": "\nKjo mund të tregojë një gabim në software.",
"Stalker"
]
},
- "tog-underline": "Ð\9fодвлаÑ\87еÑ\9aе веза:",
+ "tog-underline": "Ð\9fодвлаÑ\87еÑ\9aе линкова:",
"tog-hideminor": "Сакриј мање измене са списка скорашњих измена",
"tog-hidepatrolled": "Сакриј патролиране измене са списка скорашњих измена",
"tog-newpageshidepatrolled": "Сакриј патролиране странице са списка нових страница",
"tog-hidecategorization": "Сакриј категоризацију страница",
"tog-extendwatchlist": "Прошири списак надгледања за поглед свих промена, не само скорашњих",
"tog-usenewrc": "Групиши измене по страници у скорашњим изменама и списку надгледања",
- "tog-numberheadings": "Ð\90Ñ\83Ñ\82омаÑ\82Ñ\81ки нÑ\83меÑ\80иÑ\88и поднаÑ\81лове",
- "tog-showtoolbar": "Прикажи траку с алаткама за уређивање",
+ "tog-numberheadings": "Аутоматски нумериши наслове",
+ "tog-showtoolbar": "Прикажи траку са алаткама за уређивање",
"tog-editondblclick": "Уреди странице двоструким кликом",
"tog-editsectiononrightclick": "Омогући уређивање одељака десним кликом на њихове наслове",
"tog-watchcreations": "Додај странице које направим и датотеке које отпремим на мој списак надгледања",
"tog-watchdefault": "Додај странице и датотеке које уредим на мој списак надгледања",
"tog-watchmoves": "Додај странице и датотеке које преместим на мој списак надгледања",
- "tog-watchdeletion": "Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е и даÑ\82оÑ\82еке коÑ\98е обришем на мој списак надгледања",
+ "tog-watchdeletion": "Ð\94одаÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86е и даÑ\82оÑ\82еке коÑ\98е избришем на мој списак надгледања",
"tog-watchuploads": "Додај датотеке које отпремим на мој списак надгледања",
"tog-watchrollback": "Додај странице на којима сам извршио враћање измена на мој списак надгледања",
"tog-minordefault": "Означавај све измене као мање",
"tog-enotifwatchlistpages": "Пошаљи ми имејл када се промени страница или датотека са мог списка надгледања",
"tog-enotifusertalkpages": "Пошаљи ми имејл кад се промени моја корисничка страница за разговор",
"tog-enotifminoredits": "Пошаљи ми имејл и код мањих измена страница и датотека",
- "tog-enotifrevealaddr": "Откриј моју имејл адресу у порукама обавештења",
+ "tog-enotifrevealaddr": "Откриј моју имејл-адресу у порукама обавештења",
"tog-shownumberswatching": "Прикажи број корисника који надгледају",
"tog-oldsig": "Ваш постојећи потпис:",
- "tog-fancysig": "Сматрај потпис као викитекст (без самоповезивања)",
+ "tog-fancysig": "Сматрај потпис као викитекст (без аутоматског линка)",
"tog-uselivepreview": "Прикажи претпреглед без поновног учитавања странице",
"tog-forceeditsummary": "Упозори ме када не унесем резиме измене",
"tog-watchlisthideown": "Сакриј моје измене са списка надгледања",
"tog-diffonly": "Не приказуј садржај странице испод разлика",
"tog-showhiddencats": "Прикажи скривене категорије",
"tog-norollbackdiff": "Не приказуј разлику након извршеног враћања",
- "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање с несачуваним променама",
- "tog-prefershttps": "Увек користи сигурну везу док сам пријављен/а.",
+ "tog-useeditwarning": "Упозори ме када напуштам страницу за уређивање са несачуваним променама",
+ "tog-prefershttps": "Увек коÑ\80иÑ\81Ñ\82и Ñ\81игÑ\83Ñ\80нÑ\83 везÑ\83 док Ñ\81ам пÑ\80иÑ\98авÑ\99ен/на.",
"underline-always": "увек",
"underline-never": "никад",
"underline-default": "према теми или прегледачу",
"listingcontinuesabbrev": "наст.",
"index-category": "Пописане странице",
"noindex-category": "Непописане странице",
- "broken-file-category": "Странице с неисправним везама до датотека",
+ "broken-file-category": "Странице са неисправним линковима до датотека",
"categoryviewer-pagedlinks": "$1 ($2)",
"category-header-numerals": "$1–$2",
"about": "О нама",
"tagline": "Извор: {{SITENAME}}",
"help": "Помоћ",
"search": "Претрага",
- "search-ignored-headings": " #<!-- не меÑ\9aаÑ\98Ñ\82е ниÑ\88Ñ\82а Ñ\83 овом Ñ\80едÑ\83 --> <pre>\n# Ð\9dаÑ\81лови коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ени пÑ\80и пÑ\80еÑ\82Ñ\80ази.\n# Ð\9fÑ\80омене Ñ\81Ñ\83 видÑ\99иве одмаÑ\85 након Ñ\88Ñ\82о Ñ\81е Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\81ловом попиÑ\88е.\n# Ð\9cожеÑ\82е изнÑ\83диÑ\82и поновно попиÑ\81иваÑ\9aе â\80\9eнÑ\83лÑ\82омâ\80\9d изменом.\n# СинÑ\82акÑ\81а Ñ\98е Ñ\81ледеÑ\9bа:\n# * Сваки Ñ\80ед коÑ\98и запоÑ\87иÑ\9aе знаком â\80\9e#â\80\9d Ñ\98е коменÑ\82аÑ\80.\n# * Сваки не пÑ\80азни Ñ\80ед Ñ\98е Ñ\82аÑ\87ан наÑ\81лов коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ен, Ñ\81 Ñ\82им да Ñ\81е Ñ\80азликÑ\83Ñ\98Ñ\83 мала и велика Ñ\81лова и Ñ\81ве оÑ\81Ñ\82ало\nРеÑ\84еÑ\80енÑ\86е\nСпоÑ\99аÑ\88Ñ\9aе везе\nТакође погледајте\n #</pre> <!-- не мењајте ништа у овом реду -->",
+ "search-ignored-headings": " #<!-- не меÑ\9aаÑ\98Ñ\82е ниÑ\88Ñ\82а Ñ\83 овом Ñ\80едÑ\83 --> <pre>\n# Ð\9dаÑ\81лови коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ени пÑ\80и пÑ\80еÑ\82Ñ\80ази.\n# Ð\9fÑ\80омене Ñ\81Ñ\83 видÑ\99иве одмаÑ\85 након Ñ\88Ñ\82о Ñ\81е Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\81ловом попиÑ\88е.\n# Ð\9cожеÑ\82е изнÑ\83диÑ\82и поновно попиÑ\81иваÑ\9aе â\80\9eнÑ\83лÑ\82омâ\80\9d изменом.\n# СинÑ\82акÑ\81а Ñ\98е Ñ\81ледеÑ\9bа:\n# * Сваки Ñ\80ед коÑ\98и запоÑ\87иÑ\9aе знаком â\80\9e#â\80\9d Ñ\98е коменÑ\82аÑ\80.\n# * Сваки не пÑ\80азни Ñ\80ед Ñ\98е Ñ\82аÑ\87ан наÑ\81лов коÑ\98и Ñ\9bе биÑ\82и занемаÑ\80ен, Ñ\81 Ñ\82им да Ñ\81е Ñ\80азликÑ\83Ñ\98Ñ\83 мала и велика Ñ\81лова и Ñ\81ве оÑ\81Ñ\82ало\nРеÑ\84еÑ\80енÑ\86е\nСпоÑ\99аÑ\88Ñ\9aи линкови\nТакође погледајте\n #</pre> <!-- не мењајте ништа у овом реду -->",
"searchbutton": "Претражи",
"go": "Иди",
"searcharticle": "Иди",
"history_small": "историја",
"updatedmarker": "ажурирано од моје последње посете",
"printableversion": "За штампање",
- "permalink": "ТÑ\80аÑ\98на веза",
+ "permalink": "ТÑ\80аÑ\98ни линк",
"print": "Штампај",
"view": "Погледај",
"view-foreign": "Погледај на пројекту $1",
"edit-local": "Уреди локални опис",
"create": "Направи",
"create-local": "Додај локални опис",
- "delete": "Ð\9eбриши",
- "undelete_short": "Ð\92Ñ\80аÑ\82и {{PLURAL:$1|обÑ\80иÑ\81анÑ\83 изменÑ\83|$1 обÑ\80иÑ\81ане измене|$1 обрисаних измена}}",
- "viewdeleted_short": "Ð\9fогледаÑ\98 {{PLURAL:$1|Ñ\98еднÑ\83 обÑ\80иÑ\81анÑ\83 изменÑ\83|$1 обÑ\80иÑ\81ане измене|$1 обрисаних измена}}",
+ "delete": "Ð\98збриши",
+ "undelete_short": "Ð\92Ñ\80аÑ\82и {{PLURAL:$1|избÑ\80иÑ\81анÑ\83 изменÑ\83|$1 избÑ\80иÑ\81ане измене|$1 избрисаних измена}}",
+ "viewdeleted_short": "Ð\9fогледаÑ\98 {{PLURAL:$1|Ñ\98еднÑ\83 избÑ\80иÑ\81анÑ\83 изменÑ\83|$1 избÑ\80иÑ\81ане измене|$1 избрисаних измена}}",
"protect": "Заштити",
"protect_change": "промени",
"unprotect": "Промени заштиту",
"confirmable-no": "Не",
"thisisdeleted": "Погледај или врати $1?",
"viewdeleted": "Погледај $1?",
- "restorelink": "{{PLURAL:$1|Ñ\98еднÑ\83 обÑ\80иÑ\81анÑ\83 изменÑ\83|$1 обÑ\80иÑ\81ане измене|$1 обрисаних измена}}",
+ "restorelink": "{{PLURAL:$1|Ñ\98еднÑ\83 избÑ\80иÑ\81анÑ\83 изменÑ\83|$1 избÑ\80иÑ\81ане измене|$1 избрисаних измена}}",
"feedlinks": "Фид:",
"feed-invalid": "Неважећи тип пријаве на фид.",
"feed-unavailable": "Фидови синдикације нису доступни",
"nstab-category": "Категорија",
"mainpage-nstab": "Главна страна",
"nosuchaction": "Нема такве радње",
- "nosuchactiontext": "РадÑ\9aа наведена Ñ\83 URL-Ñ\83 ниÑ\98е валидна.\nÐ\9cожда Ñ\81Ñ\82е оÑ\82кÑ\83Ñ\86али погÑ\80еÑ\88ан URL-а или Ñ\81Ñ\82е пÑ\80аÑ\82или покваÑ\80енÑ\83 везÑ\83.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.",
+ "nosuchactiontext": "РадÑ\9aа коÑ\98а Ñ\98е наведена Ñ\83 URL-Ñ\83 ниÑ\98е важеÑ\9bа.\nÐ\9cожда Ñ\81Ñ\82е оÑ\82кÑ\83Ñ\86али погÑ\80еÑ\88ан URL-а или Ñ\81Ñ\82е пÑ\80аÑ\82или покваÑ\80ен линк.\nОво такође може да указује на грешку у софтверу који користи {{SITENAME}}.",
"nosuchspecialpage": "Нема такве посебне странице",
"nospecialpagetext": "<strong>Захтевали сте невалидну посебну страницу.</strong>\n\nСписак валидних посебних страница може да се пронађе на „[[Special:SpecialPages|{{int:specialpages}}]]”.",
"error": "Грешка",
"readonly": "База података је закључана",
"enterlockreason": "Унесите разлог за закључавање, укључујући и време откључавања",
"readonlytext": "База података је тренутно закључана, што значи да је није могуће мењати.\n\nСистемски администратор је навео следеће објашњење: $1",
- "missing-article": "ТекÑ\81Ñ\82 Ñ\81Ñ\82Ñ\80аниÑ\86е под називом â\80\9e$1â\80\9c ($2) ниÑ\98е пÑ\80онаÑ\92ен.\n\nУзÑ\80ок ове гÑ\80еÑ\88ке Ñ\98е обиÑ\87но заÑ\81Ñ\82аÑ\80ела измена или веза до обÑ\80иÑ\81ане Ñ\81Ñ\82Ñ\80аниÑ\86е.\n\nÐ\90ко Ñ\81е не Ñ\80ади о Ñ\82оме, онда Ñ\81Ñ\82е веÑ\80оваÑ\82но пÑ\80онаÑ\88ли гÑ\80еÑ\88кÑ\83 Ñ\83 Ñ\81оÑ\84Ñ\82веÑ\80Ñ\83.\nÐ\9fÑ\80иÑ\98авиÑ\82е Ñ\98е [[Special:ListUsers/sysop|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80Ñ\83]] Ñ\83з одговаÑ\80аÑ\98Ñ\83Ñ\9bÑ\83 везÑ\83.",
+ "missing-article": "ТекÑ\81Ñ\82 Ñ\81Ñ\82Ñ\80аниÑ\86е под називом â\80\9e$1â\80\9c ($2) ниÑ\98е пÑ\80онаÑ\92ен.\n\nУзÑ\80ок ове гÑ\80еÑ\88ке Ñ\98е обиÑ\87но заÑ\81Ñ\82аÑ\80ела измена или линк до избÑ\80иÑ\81ане Ñ\81Ñ\82Ñ\80аниÑ\86е.\n\nÐ\90ко Ñ\81е не Ñ\80ади о Ñ\82оме, онда Ñ\81Ñ\82е веÑ\80оваÑ\82но пÑ\80онаÑ\88ли гÑ\80еÑ\88кÑ\83 Ñ\83 Ñ\81оÑ\84Ñ\82веÑ\80Ñ\83.\nÐ\9fÑ\80иÑ\98авиÑ\82е Ñ\98е [[Special:ListUsers/sysop|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80Ñ\83]] Ñ\83з одговаÑ\80аÑ\98Ñ\83Ñ\9bи линк.",
"missingarticle-rev": "(ревизија#: $1)",
"missingarticle-diff": "(разлика: $1, $2)",
"readonly_lag": "База података је аутоматски закључана да би се секундарни сервери базе података ускладили с главним.",
"internalerror-fatal-exception": "Грешка необрађеног изузетка типа „$1“",
"filecopyerror": "Не могу да копирам датотеку „$1“ у „$2“.",
"filerenameerror": "Не могу да преименујем датотеку „$1“ у „$2“.",
- "filedeleteerror": "Ð\9dе могÑ\83 да обришем датотеку „$1“.",
+ "filedeleteerror": "Ð\9dе могÑ\83 да избришем датотеку „$1“.",
"directorycreateerror": "Не могу да направим директоријум „$1“.",
"directoryreadonlyerror": "Директоријум „$1“ је само за читање.",
"directorynotreadableerror": "Директоријум „$1“ није читљив.",
"unexpected": "Неочекивана вредност: „$1“=„$2“.",
"formerror": "Грешка: не могу да пошаљем образац.",
"badarticleerror": "Ова радња се не може извршити на овој страници.",
- "cannotdelete": "Ð\9dе могÑ\83 да обÑ\80иÑ\88ем Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 или даÑ\82оÑ\82екÑ\83 â\80\9e$1â\80\9c.\nÐ\92еÑ\80оваÑ\82но Ñ\98Ñ\83 Ñ\98е неко дÑ\80Ñ\83ги обрисао.",
- "cannotdelete-title": "Ð\9dе могÑ\83 да обришем страницу „$1“",
+ "cannotdelete": "Ð\9dе могÑ\83 да избÑ\80иÑ\88ем Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 или даÑ\82оÑ\82екÑ\83 â\80\9e$1â\80\9c.\nÐ\9cогÑ\83Ñ\9bе Ñ\98е да Ñ\98Ñ\83 Ñ\98е неко веÑ\9b избрисао.",
+ "cannotdelete-title": "Ð\9dе могÑ\83 да избришем страницу „$1“",
"delete-hook-aborted": "Брисање је прекинула кука.\nНије дато никакво образложење.",
"no-null-revision": "Не могу да направим нову ништавну ревизију странице „$1“",
"badtitle": "Лош наслов",
- "badtitletext": "Ð\97аÑ\85Ñ\82евани наслов странице је неважећи, празан или је погрешно повезан међујезички или међувики наслов.\nМожда садржи један или више знакова који се не могу користити у насловима.",
+ "badtitletext": "ТÑ\80ажени наслов странице је неважећи, празан или је погрешно повезан међујезички или међувики наслов.\nМожда садржи један или више знакова који се не могу користити у насловима.",
"title-invalid-empty": "Тражено име странице је празно или садржи само назив именског простора.",
"title-invalid-utf8": "Тражени назив странице садржи неважећи UTF-8 знак.",
- "title-invalid-interwiki": "Тражени наслов странице садржи унутрашњу вики везу која не може бити кориштена у насловима.",
+ "title-invalid-interwiki": "Тражени наслов странице садржи унутрашњи вики линк који не може да се користи у насловима.",
"title-invalid-talk-namespace": "Тражени наслов странице се односи на страницу за разговор која не може постојати.",
"title-invalid-characters": "Тражени наслов има неважеће знакове: „$1“.",
"title-invalid-relative": "Наслов има релативну путању. Релативни наслови страница (./, ../) нису важећи јер ће често бити недоступни у корисничком прегледачу.",
"title-invalid-magic-tilde": "Тражени наслов странице садржи неважећи след магичног знака тилда (<nowiki>~~~</nowiki>).",
"title-invalid-too-long": "Тражени назив странице је предугачак. Не сме бити дужи од $1 {{PLURAL:$1|бајта|бајтова}} у UTF-8 кодирању.",
- "title-invalid-leading-colon": "Ð\97аÑ\85Ñ\82евани наслов странице садржи неважећу двотачку на почетку.",
+ "title-invalid-leading-colon": "ТÑ\80ажени наслов странице садржи неважећу двотачку на почетку.",
"perfcached": "Следећи подаци су кеширани и можда нису ажурирани. У кешу {{PLURAL:$1|је доступан највише један резултат|су доступна највише $1 резултата|је доступно највише $1 резултата}}.",
"perfcachedts": "Следећи подаци су кеширани и последњи пут ажурирани на датум $2 у $3 ч. У кешу {{PLURAL:$4|је доступан највише један резултат|су доступна највише $4 резултата|је доступно највише $4 резултата}}.",
"querypage-no-updates": "Ажурирање ове странице је тренутно онемогућено.\nПодаци који се овде налазе могу бити застарели.",
"titleprotected": "Овај назив је [[User:$1|$1]] заштитио од прављења. Разлог: <em>$2</em>.",
"filereadonlyerror": "Не могу да изменим датотеку „$1“ јер је ризница „$2“ у режиму за читање.\n\nСистемски администратор је навео следеће објашњење: „$3“.",
"invalidtitle": "Неважећи наслов",
- "invalidtitle-knownnamespace": "Ð\9dеиÑ\81пÑ\80аван наÑ\81лов Ñ\81 именским простором „$2“ и текстом „$3“",
- "invalidtitle-unknownnamespace": "Ð\9dеиÑ\81пÑ\80аван наÑ\81лов Ñ\81 именским простором бр. $1 и текстом „$2“",
+ "invalidtitle-knownnamespace": "Ð\9dеважеÑ\9bи наÑ\81лов Ñ\81а именским простором „$2“ и текстом „$3“",
+ "invalidtitle-unknownnamespace": "Ð\9dеважеÑ\9bи наÑ\81лов Ñ\81а непознаÑ\82им именским простором бр. $1 и текстом „$2“",
"exception-nologin": "Нисте пријављени",
"exception-nologin-text": "Пријавите се да бисте приступили овој страници или радњи.",
"exception-nologin-text-manual": "Морате бити $1 да бисте приступили овој страници или радњи.",
- "virus-badscanner": "Ð\9dеиÑ\81пÑ\80авно подеÑ\88аваÑ\9aе: непознати скенер за вирусе: <em>$1</em>",
+ "virus-badscanner": "Ð\9bоÑ\88а конÑ\84игÑ\83Ñ\80аÑ\86иÑ\98а: непознати скенер за вирусе: <em>$1</em>",
"virus-scanfailed": "скенирање није успело (код $1)",
"virus-unknownscanner": "непознати антивирус:",
"logouttext": "<strong>Сада сте одјављени.</strong>\n\nЗапамтите да неке странице могу наставити да се приказују као да сте још увек пријављени, све док не очистите кеш свог прегледача.",
"userlogin-loggedin": "Већ сте пријављени као {{GENDER:$1|$1}}.\nКористите доњи образац да бисте се пријавили као други корисник.",
"userlogin-reauth": "Морате да се поново пријавите да бисте потврдили да сте {{GENDER:$1|$1}}.",
"userlogin-createanother": "Отвори још један налог",
- "createacct-emailrequired": "Имејл адреса",
- "createacct-emailoptional": "Имејл адреса (опционално)",
- "createacct-email-ph": "Унесите своју имејл адресу",
- "createacct-another-email-ph": "Унесите имејл адресу",
- "createaccountmail": "Користите привремену, случајно створену лозинку и пошаљите на наведену имејл адресу",
+ "createacct-emailrequired": "Имејл-адреса",
+ "createacct-emailoptional": "Имејл-адреса (опционално)",
+ "createacct-email-ph": "Унесите своју имејл-адресу",
+ "createacct-another-email-ph": "Унесите имејл-адресу",
+ "createaccountmail": "Користите привремену, случајну лозинку и пошаљите је на наведену имејл-адресу",
"createaccountmail-help": "Може се користити да се некоме отвори налог без сазнања лозинке.",
"createacct-realname": "Право име (опционално)",
"createacct-reason": "Разлог",
"nocookiesfornew": "Кориснички налог није отворен јер његов извор није потврђен.\nОмогућите колачиће на прегледачу и поново учитајте страницу.",
"nocookiesforlogin": "{{int:nocookieslogin}}",
"createacct-loginerror": "Налог је успешно направљен, али се не можете аутоматски пријавити. Пређите на [[Special:UserLogin|ручно пријављивање]].",
- "noname": "Унели Ñ\81Ñ\82е неиÑ\81пÑ\80авно корисничко име.",
+ "noname": "Ð\9dиÑ\81Ñ\82е навели важеÑ\9bе корисничко име.",
"loginsuccesstitle": "Успешно пријављивање",
"loginsuccess": "<strong>Пријављени сте на {{SITENAME}} као „$1”.</strong>",
"nosuchuser": "Не постоји корисник с именом „$1“.\nКорисничка имена су осетљива на мала и велика слова.\nПроверите да ли сте га добро унели или [[Special:CreateAccount|отворите нови налог]].",
"nosuchusershort": "Корисник с именом „$1“ не постоји.\nПроверите да ли сте правилно написали.",
"nouserspecified": "Морате навести корисничко име.",
"login-userblocked": "{{GENDER:$1|Овај корисник је блокиран|Ова корисница је блокирана|Овај корисник је блокиран}}. Пријава није дозвољена.",
- "wrongpassword": "Унели сте неисправно корисничко име или лозинку. Покушајте поново.",
+ "wrongpassword": "Унели сте неисправно корисничко име или лозинку.\nПокушајте поново.",
"wrongpasswordempty": "Нисте унели лозинку. Покушајте поново.",
"passwordtooshort": "Лозинка мора имати најмање {{PLURAL:$1|један знак|$1 знака|$1 знакова}}.",
"passwordtoolong": "Лозинке не могу бити дуже од {{PLURAL:$1|$1 знака|$1 знакова}}.",
"mailmypassword": "Ресетуј лозинку",
"passwordremindertitle": "{{SITENAME}} — привремена лозинка",
"passwordremindertext": "Неко са IP адресе $1 је затражио нову лозинку на викију {{SITENAME}} ($4).\nСтворена је привремена лозинка за {{GENDER:$2|корисника|корисницу|корисника}} $2 која гласи $3.\nУколико је ово ваш захтев, сада се пријавите и поставите нову лозинку.\nПривремена лозинка истиче за {{PLURAL:$5|један дан|$5 дана}}.\n\nАко је неко други затражио промену лозинке, или сте се сетили ваше лозинке и не желите да је мењате, занемарите ову поруку.",
- "noemail": "Не постоји имејл адреса за {{GENDER:$1|корисника|корисницу}} $1.",
- "noemailcreate": "Ð\9cоÑ\80аÑ\82е да наведеÑ\82е валиднÑ\83 имеÑ\98л адресу.",
- "passwordsent": "Нова лозинка је послата на имејл адресу {{GENDER:$1|корисника|кориснице|корисника}} $1.\nПријавите се пошто је примите.",
+ "noemail": "Не постоји имејл-адреса за {{GENDER:$1|корисника|корисницу}} $1.",
+ "noemailcreate": "Ð\9cоÑ\80аÑ\82е да наведеÑ\82е важеÑ\9bÑ\83 имеÑ\98л-адресу.",
+ "passwordsent": "Нова лозинка је послата на имејл-адресу {{GENDER:$1|корисника|кориснице}} $1.\nПоново се пријавите након што је примите.",
"blocked-mailpassword": "Уређивање са ваше IP адресе је блокирано. Ради спречавања злоупотребе, забрањена је и функција враћања лозинке са ње.",
- "eauthentsent": "Ð\9dа наведенÑ\83 имеÑ\98л адÑ\80еÑ\81Ñ\83 Ñ\98е поÑ\81лаÑ\82 поÑ\82вÑ\80дни код.\nÐ\9fÑ\80е него Ñ\88Ñ\82о поÑ\88аÑ\99емо даÑ\99Ñ\9aе поÑ\80Ñ\83ке, пÑ\80аÑ\82иÑ\82е Ñ\83пÑ\83Ñ\82Ñ\81Ñ\82ва Ñ\81 имеÑ\98ла да биÑ\81Ñ\82е поÑ\82вÑ\80дили да Ñ\81Ñ\82е Ð\92и оÑ\82воÑ\80или налог.",
+ "eauthentsent": "Ð\98меÑ\98л о поÑ\82вÑ\80ди Ñ\98е поÑ\81лаÑ\82 на наведенÑ\83 имеÑ\98л-адÑ\80еÑ\81Ñ\83.\nÐ\9fÑ\80е било коÑ\98иÑ\85 дÑ\80Ñ\83гиÑ\85 Ñ\81лаÑ\9aа имеÑ\98лова на налог, моÑ\80аÑ\9bеÑ\82е пÑ\80аÑ\82иÑ\82и Ñ\83пÑ\83Ñ\82Ñ\81Ñ\82ва Ñ\83 имеÑ\98лÑ\83 да биÑ\81Ñ\82е поÑ\82вÑ\80дили да Ñ\98е налог заиÑ\81Ñ\82а ваÑ\88.",
"throttled-mailpassword": "Порука за промену лозинке је послата у {{PLURAL:$1|1=последњих сат времена|последња $1 сата|последњих $1 сати}}.\nДа бисмо спречили злоупотребу, подсетник шаљемо само једном у року од {{PLURAL:$1|1=сат времена|$1 сата|$1 сати}}.",
"mailerror": "Грешка при слању поруке: $1",
"acct_creation_throttle_hit": "Посетиоци овог викија који користе вашу IP адресу су већ отворили {{PLURAL:$1|1=један налог|$1 налога}} претходни $2, што је највећи дозвољени број у том временском периоду.\nЗбог тога посетиоци с ове IP адресе тренутно не могу отворити више налога.",
- "emailauthenticated": "Ваша имејл адреса је потврђена на дан $2 у $3 ч.",
- "emailnotauthenticated": "Ваша имејл адреса још увек није потврђена.\nИмејл неће бити послат ни у једном од следећих случајева.",
- "noemailprefs": "Наведите имејл адресу у својим подешавањима за рад ових могућности.",
- "emailconfirmlink": "Потврдите своју имејл адресу",
- "invalidemailaddress": "Имејл адреса не може бити прихваћена јер је невалидног облика.\nУнесите исправну адресу или оставите празно поље.",
- "cannotchangeemail": "Ð\9dа овом викиÑ\98Ñ\83 не можеÑ\82е пÑ\80омениÑ\82и имеÑ\98л адÑ\80еÑ\81Ñ\83 налога.",
+ "emailauthenticated": "Ваша имејл-адреса је потврђена на дан $2 у $3 ч.",
+ "emailnotauthenticated": "Ваша имејл-адреса још није потврђена.\nНиједан имејл неће да буде послат ни у једном од следећих случајева.",
+ "noemailprefs": "Наведите имејл-адресу у својим подешавањима за оспособљавање ових могућности.",
+ "emailconfirmlink": "Потврдите своју имејл-адресу",
+ "invalidemailaddress": "Имејл-адреса не може да буде прихваћена јер је у неважећем облику.\nУнесите исправну адресу или оставите празно поље.",
+ "cannotchangeemail": "Ð\98меÑ\98л-адÑ\80еÑ\81е налога не могÑ\83 да Ñ\81е пÑ\80омене на овом викиÑ\98Ñ\83.",
"emaildisabled": "Овај сајт не може да шаље имејлове.",
"accountcreated": "Налог је отворен",
"accountcreatedtext": "Кориснички налог [[{{ns:User}}:$1|$1]] ([[{{ns:User talk}}:$1|talk]]) је отворен.",
"createaccount-title": "Отварање корисничког налога за {{SITENAME}}",
- "createaccount-text": "Неко је отворио налог с вашом имејл адресом на {{SITENAME}} ($4) под именом $2 и лозинком $3.\nПријавите се и промените своју лозинку.\n\nАко је ово грешка, занемарите ову поруку.",
+ "createaccount-text": "Неко је отворио налог са вашом имејл-адресом на пројекту {{SITENAME}} ($4) под именом „$2“ и са лозинком „$3“.\nОдмах требате да се пријавите и промените своју лозинку.\n\nМожете да занемарите ову поруку, ако је овај налог отворен грешком.",
"login-throttled": "Превише пута сте покушали да се пријавите.\nСачекајте $1 пре него што покушате поново.",
"login-abort-generic": "Неуспешна пријава – прекинуто",
"login-migrated-generic": "Ваш налог је мигриран и ваше корисничко више не постоји на овом викију.",
"loginlanguagelabel": "Језик: $1",
- "suspicious-userlogout": "Ваш захтев за одјаву је одбијен јер је послат од стране неисправног прегледача или посредника.",
+ "suspicious-userlogout": "Ваш захтев за одјаву је одбијен јер изгледа да га је послао покварени прегледач или кеширани посредник.",
"createacct-another-realname-tip": "Право име је опционално.\nАко одаберете да га наведете, биће коришћено за приписивање вашег рада.",
"pt-login": "Пријави ме",
"pt-login-button": "Пријави ме",
"pt-createaccount": "Отвори налог",
"pt-userlogout": "Одјави ме",
"php-mail-error-unknown": "Непозната грешка у функцији PHP mail().",
- "user-mail-no-addy": "Покушали сте да пошаљете имејл без имејл адресе.",
+ "user-mail-no-addy": "Покушали сте да пошаљете имејл без имејл-адресе.",
"user-mail-no-body": "Покушано слање имејла с празним или неразумно кратким садржајем.",
"changepassword": "Промена лозинке",
"resetpass_announce": "Да бисте завршили пријаву, подесите нову лозинку овде.",
"botpasswords-label-create": "Направи",
"botpasswords-label-update": "Ажурирај",
"botpasswords-label-cancel": "Откажи",
- "botpasswords-label-delete": "Ð\9eбриши",
+ "botpasswords-label-delete": "Ð\98збриши",
"botpasswords-label-resetpassword": "Ресетуј лозинку",
"botpasswords-label-grants": "Применљиве дозволе:",
"botpasswords-label-grants-column": "Одобрено",
"botpasswords-bad-appid": "Име бота „$1” није валидно.",
"botpasswords-insert-failed": "Неуспело додавање бота под именом „$1”. Можда је већ додат?",
- "botpasswords-update-failed": "Ð\9dеÑ\83Ñ\81пело ажÑ\83Ñ\80иÑ\80аÑ\9aе боÑ\82а под називом â\80\9e$1â\80\9d. Ð\94а ли Ñ\98е обрисан?",
+ "botpasswords-update-failed": "Ð\9dеÑ\83Ñ\81пело ажÑ\83Ñ\80иÑ\80аÑ\9aе боÑ\82а под називом â\80\9e$1â\80\9d. Ð\94а ниÑ\98е избрисан?",
"botpasswords-created-title": "Направљена лозинка бота",
"botpasswords-created-body": "Лозинка за бота „$1” корисника „$2” је направљена.",
"botpasswords-updated-title": "Лозинка бота промењена",
"botpasswords-updated-body": "Лозинка за бота „$1” корисника „$2” је ажурирана.",
- "botpasswords-deleted-title": "Ð\9eбрисана лозинка бота",
- "botpasswords-deleted-body": "Лозинка за бота „$1” корисника „$2” је обрисана.",
+ "botpasswords-deleted-title": "Ð\98збрисана лозинка бота",
+ "botpasswords-deleted-body": "Лозинка за бота „$1” {{GENDER:$2|корисника|кориснице}} „$2” је избрисана.",
"botpasswords-no-provider": "BotPasswordsSessionProvider није доступан.",
"botpasswords-restriction-failed": "Не можете се пријавити због ограничења лозинки за ботове.",
"botpasswords-not-exist": "Корисник „$1“ нема лозинку бота „$2“.",
"passwordreset-emaildisabled": "Имејл је онемогућен на овом викију.",
"passwordreset-username": "Корисничко име:",
"passwordreset-domain": "Домен:",
- "passwordreset-email": "Имејл адреса:",
+ "passwordreset-email": "Имејл-адреса:",
"passwordreset-emailtitle": "Детаљи налога на викију {{SITENAME}}",
- "passwordreset-emailtext-ip": "Ð\9dеко (веÑ\80оваÑ\82но Ð\92и, Ñ\81 IP адÑ\80еÑ\81е $1) заÑ\82Ñ\80ажио Ñ\98е Ñ\80еÑ\81еÑ\82оваÑ\9aе ваÑ\88е \nлозинке за пÑ\80оÑ\98екаÑ\82 {{SITENAME}} ($4). СледеÑ\9bи коÑ\80иÑ\81ниÑ\87ки {{PLURAL:$3|налог Ñ\98е повезан|налози Ñ\81Ñ\83 повезани}} \nÑ\81 овом имеÑ\98л адÑ\80еÑ\81ом:\n\n$2\n\n{{PLURAL:$3|Ð\9eва пÑ\80ивÑ\80емена лозинка|Ð\9eве пÑ\80ивÑ\80емене лозинке}} Ñ\9bе иÑ\81Ñ\82еÑ\9bи за {{PLURAL:$5|Ñ\98едан дан|$5 дана}}.\nТÑ\80ебаÑ\82е да Ñ\81е пÑ\80иÑ\98авиÑ\82е и одабеÑ\80иÑ\82е новÑ\83 лозинкÑ\83 одмаÑ\85. Ако је неко други направио овај \nзахтев или сте се сетили своје првобитне лозинке, а не \nжелите да је промените, можете да занемарите ову поруку и наставите да користите своју стару \nлозинку.",
- "passwordreset-emailtext-user": "{{GENDER:$1|Корисник је затражио|Корисница је затражила}} подсетник о подацима за пријаву на викију {{SITENAME}} ($4).\nСледећи {{PLURAL:$3|кориснички налог је повезан|кориснички налози су повезани}} с овом имејл адресом:\n\n$2\n\n{{PLURAL:$3|Привремена лозинка истиче|Привремене лозинке истичу}} за {{PLURAL:$5|један дан|$5 дана}}.\nПријавите се и изаберите нову лозинку. Ако је неко други захтевао ову радњу или сте се сетили лозинке и не желите да је мењате, занемарите ову поруку.",
+ "passwordreset-emailtext-ip": "Ð\9dеко (веÑ\80оваÑ\82но ви, Ñ\81а IP адÑ\80еÑ\81е $1) заÑ\82Ñ\80ажио Ñ\98е Ñ\80еÑ\81еÑ\82оваÑ\9aе ваÑ\88е \nлозинке за пÑ\80оÑ\98екаÑ\82 {{SITENAME}} ($4). СледеÑ\9bи коÑ\80иÑ\81ниÑ\87ки {{PLURAL:$3|налог Ñ\98е повезан|налози Ñ\81Ñ\83 повезани}} \nÑ\81а овом имеÑ\98л адÑ\80еÑ\81ом:\n\n$2\n\n{{PLURAL:$3|Ð\9eва пÑ\80ивÑ\80емена лозинка|Ð\9eве пÑ\80ивÑ\80емене лозинке}} Ñ\9bе иÑ\81Ñ\82еÑ\9bи за {{PLURAL:$5|Ñ\98едан дан|$5 дана}}.\nÐ\9eдмаÑ\85 Ñ\82Ñ\80ебаÑ\82е да Ñ\81е пÑ\80иÑ\98авиÑ\82е и одабеÑ\80иÑ\82е новÑ\83 лозинкÑ\83. Ако је неко други направио овај \nзахтев или сте се сетили своје првобитне лозинке, а не \nжелите да је промените, можете да занемарите ову поруку и наставите да користите своју стару \nлозинку.",
+ "passwordreset-emailtext-user": "{{GENDER:$1|Корисник је затражио|Корисница је затражила}} подсетник о подацима за пријаву на викију {{SITENAME}} ($4).\nСледећи {{PLURAL:$3|кориснички налог је повезан|кориснички налози су повезани}} са овом имејл-адресом:\n\n$2\n\n{{PLURAL:$3|Привремена лозинка истиче|Привремене лозинке истичу}} за {{PLURAL:$5|један дан|$5 дана}}.\nПријавите се и изаберите нову лозинку. Ако је неко други захтевао ову радњу или сте се сетили лозинке и не желите да је мењате, занемарите ову поруку.",
"passwordreset-emailelement": "Корисничко име: \n$1\n\nПривремена лозинка: \n$2",
- "passwordreset-emailsentemail": "Ð\90ко Ñ\98е ово имеÑ\98л адÑ\80еÑ\81а повезана Ñ\81а ваÑ\88им налогом, подÑ\81еÑ\82ник о лозинÑ\86и Ñ\9bе биÑ\82и поÑ\81лаÑ\82 на имеÑ\98л.",
- "passwordreset-emailsentusername": "Ако сте навели имејл адресу приликом регистрације, биће послат имејл за ресетовање лозинке.",
+ "passwordreset-emailsentemail": "Ð\90ко Ñ\98е ова имеÑ\98л-адÑ\80еÑ\81а повезана Ñ\81а ваÑ\88им налогом, онда Ñ\9bе имеÑ\98л о Ñ\80еÑ\81еÑ\82оваÑ\9aÑ\83 лозинке биÑ\82и поÑ\81лаÑ\82.",
+ "passwordreset-emailsentusername": "Ако постоји имејл-адреса повезана са овим корисничким именом, онда ће имејл о ресетовању лозинке бити послат.",
"passwordreset-nocaller": "Позивалац се мора навести",
"passwordreset-nosuchcaller": "Позивалац не постоји: $1",
"passwordreset-ignored": "Ресетовање лозинке није успело. Можда послужилац није конфигурисан?",
- "passwordreset-invalidemail": "Ð\9dеиÑ\81пÑ\80авна имеÑ\98л адреса",
+ "passwordreset-invalidemail": "Ð\9dеважеÑ\9bа имеÑ\98л-адреса",
"passwordreset-nodata": "Корисничко име и адреса е-поште нису наведени",
- "changeemail": "Промена или уклањање имејл адресе",
- "changeemail-header": "Попуните овај образац да би сте променили вашу имејл адресу. Ако жели да ускратите приступ било којој имејл адреси вашем налогу, оставите празно поље за нову имејл адресу приликом попуњавање обрасца.",
+ "changeemail": "Промена или уклањање имејл-адресе",
+ "changeemail-header": "Попуните овај образац да би сте променили вашу имејл-адресу. Ако бисте желели да уклоните повезаност било које имејл-адресе са вашег налога, оставите празно поље за нову имејл-адресу када шаљете образац.",
"changeemail-no-info": "Морате бити пријављени да бисте приступили овој страници.",
- "changeemail-oldemail": "Актуелна имејл адреса:",
- "changeemail-newemail": "Нова имејл адреса:",
+ "changeemail-oldemail": "Актуелна имејл-адреса:",
+ "changeemail-newemail": "Нова имејл-адреса:",
"changeemail-none": "(ништа)",
"changeemail-password": "Ваша лозинка за пројекат {{SITENAME}}:",
"changeemail-submit": "Промени имејл",
"changeemail-throttled": "Превише пута сте покушали да се пријавите.\nМолимо вас да сачекате $1 пре него што покушате поново.",
- "changeemail-nochange": "Унесите другу имејл адресу.",
+ "changeemail-nochange": "Унесите другу имејл-адресу.",
"resettokens": "Ресетовање токена",
- "resettokens-text": "Можете поново поставити жетоне који ће вам омогућити приступ одређеним приватним подацима повезаним са вашим налогом овде.\n\nТребали бисте то да урадите ако их мимо воље поделите с неким или ако је ваш налог угрожен.",
+ "resettokens-text": "Можете поново поставити жетоне који ће вам омогућити приступ одређеним приватним подацима повезаним са вашим налогом овде.\n\nТребали бисте то да урадите ако их мимо воље поделите са неким или ако је ваш налог угрожен.",
"resettokens-no-tokens": "Нема жетона за ресетовање.",
"resettokens-tokens": "Жетони:",
"resettokens-token-label": "$1 (тренутна вредност: $2)",
"bold_tip": "Подебљан текст",
"italic_sample": "Искошен текст",
"italic_tip": "Искошен текст",
- "link_sample": "Ð\9dаÑ\81лов везе",
- "link_tip": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа веза",
- "extlink_sample": "http://www.example.com/ наÑ\81лов везе",
- "extlink_tip": "СпоÑ\99аÑ\88Ñ\9aа веза (Ñ\81 префиксом http://)",
+ "link_sample": "Ð\9dаÑ\81лов линка",
+ "link_tip": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aи линк",
+ "extlink_sample": "http://www.example.com/ наÑ\81лов линка",
+ "extlink_tip": "СпоÑ\99аÑ\88Ñ\9aи линк (Ñ\81а префиксом http://)",
"headline_sample": "Текст наслова",
"headline_tip": "Поднаслов (ниво 2)",
"nowiki_sample": "Овде уметните необликован текст",
"image_sample": "Пример.jpg",
"image_tip": "Уграђивање датотеке",
"media_sample": "Пример.ogg",
- "media_tip": "Ð\92еза",
+ "media_tip": "Ð\9bинк до даÑ\82оÑ\82еке",
"sig_tip": "Ваш потпис са временском ознаком",
"hr_tip": "Водоравна линија (користите ретко)",
"summary": "Резиме:",
"minoredit": "Ово је мања измена",
"watchthis": "Надгледај ову страницу",
"savearticle": "Сачувај страницу",
- "savechanges": "СаÑ\87Ñ\83ваÑ\98 измене",
+ "savechanges": "СаÑ\87Ñ\83ваÑ\98 пÑ\80омене",
"publishpage": "Објави страницу",
- "publishchanges": "Ð\9eбÑ\98ави измене",
+ "publishchanges": "Ð\9eбÑ\98ави пÑ\80омене",
"savearticle-start": "Сачувај страницу...",
- "savechanges-start": "СаÑ\87Ñ\83ваÑ\98 измене...",
+ "savechanges-start": "СаÑ\87Ñ\83ваÑ\98 пÑ\80омене...",
"publishpage-start": "Објави страницу...",
- "publishchanges-start": "Ð\9eбÑ\98ави измене...",
+ "publishchanges-start": "Ð\9eбÑ\98ави пÑ\80омене...",
"preview": "Претпреглед",
"showpreview": "Прикажи претпреглед",
"showdiff": "Прикажи промене",
"subject-preview": "Преглед теме:",
"previewerrortext": "Дошло је до грешке при покушају прегледа промена.",
"blockedtitle": "Корисник је блокиран",
- "blockedtext": "<strong>Ð\92аÑ\88е коÑ\80иÑ\81ниÑ\87ко име или IP адÑ\80еÑ\81а Ñ\98е блокиÑ\80ана.</strong>\n\nÐ\91локиÑ\80аÑ\9aе Ñ\98е {{GENDER:$4|извÑ\80Ñ\88ио|извÑ\80Ñ\88ила}} $1.\nРазлог Ñ\98е <em>$2</em>.\n\n* Ð\9fоÑ\87еÑ\82ак блокаде: $8\n* Ð\9aÑ\80аÑ\98 блокаде: $6\n* Ð\91локиÑ\80ани коÑ\80иÑ\81ник: $7\n\nÐ\9cожеÑ\82е да конÑ\82акÑ\82иÑ\80аÑ\82е {{GENDER:$4|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86Ñ\83}} $1 или дÑ\80Ñ\83гог [[{{MediaWiki:Grouppage-sysop}}|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80а]] да биÑ\81Ñ\82е Ñ\80азговаÑ\80али о блокади.\nÐ\9dе можеÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82 â\80\9e{{int:emailuser}}â\80\9d оÑ\81им ако Ñ\81Ñ\82е навели ваÑ\99анÑ\83 имеÑ\98л адÑ\80еÑ\81Ñ\83 Ñ\83 Ñ\81воÑ\98им [[Special:Preferences|подеÑ\88аваÑ\9aима налога]] и ниÑ\81Ñ\82е блокиÑ\80ани од коÑ\80иÑ\88Ñ\9bеÑ\9aа иÑ\81Ñ\82е.\nÐ\92аÑ\88а акÑ\82Ñ\83елна IP адÑ\80еÑ\81а Ñ\98е $3, а ID блокаде #$5.\nУкÑ\99Ñ\83Ñ\87ите све горње детаље при прављењу било каквих упита.",
+ "blockedtext": "<strong>Ð\92аÑ\88е коÑ\80иÑ\81ниÑ\87ко име или IP адÑ\80еÑ\81а Ñ\98е блокиÑ\80ана.</strong>\n\nÐ\91локиÑ\80аÑ\9aе Ñ\98е {{GENDER:$4|извÑ\80Ñ\88ио|извÑ\80Ñ\88ила}} $1.\nРазлог Ñ\98е <em>$2</em>.\n\n* Ð\9fоÑ\87еÑ\82ак блокиÑ\80аÑ\9aа: $8\n* Ð\98Ñ\81Ñ\82ек блокиÑ\80аÑ\9aа: $6\n* Ð\91локиÑ\80ани: $7\n\nÐ\9cожеÑ\82е да конÑ\82акÑ\82иÑ\80аÑ\82е {{GENDER:$4|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86Ñ\83}} $1 или дÑ\80Ñ\83гог [[{{MediaWiki:Grouppage-sysop}}|админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80а]] да биÑ\81Ñ\82е Ñ\80азговаÑ\80али о блокиÑ\80аÑ\9aÑ\83.\nÐ\9dе можеÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82 â\80\9e{{int:emailuser}}â\80\9d оÑ\81им ако Ñ\81Ñ\82е навели валиднÑ\83 имеÑ\98л адÑ\80еÑ\81Ñ\83 Ñ\83 Ñ\81воÑ\98им [[Special:Preferences|подеÑ\88аваÑ\9aима налога]] и ниÑ\81Ñ\82е блокиÑ\80ани од коÑ\80иÑ\88Ñ\9bеÑ\9aа иÑ\81Ñ\82е.\nÐ\92аÑ\88а акÑ\82Ñ\83елна IP адÑ\80еÑ\81а Ñ\98е $3, а ID блокаде #$5.\nÐ\9dаведите све горње детаље при прављењу било каквих упита.",
"autoblockedtext": "Ваша IP адреса је аутоматски блокирана јер ју је користио други корисник, кога је {{GENDER:$4|блокирао|блокирала}} $1.\nРазлог:\n\n:<em>$2</em>\n\n* Почетак блокаде: $8\n* Крај блокаде: $6\n* Име корисника: $7\n\nМожете да контактирате {{GENDER:$4|корисника|корисницу}} $1 или другог [[{{MediaWiki:Grouppage-sysop}}|администратора]] да бисте расправљали о блокади.\n\nЗапамтите да не можете да користите могућност „{{int:emailuser}}“ осим ако сте навели ваљану имејл адресу у својим [[Special:Preferences|подешавањима]].\n\nВаша актуелна IP адреса је $3, а ID блокаде $5.\nУкључите све горње детаље при прављењу било каквих упита.",
"blockednoreason": "разлог није наведен",
"whitelistedittext": "За уређивање странице је потребно да будете $1.",
"confirmedittext": "Морате да потврдите своју имејл адресу пре уређивања страница.\nПоставите и потврдите имејл адресу преко [[Special:Preferences|подешавања]].",
"nosuchsectiontitle": "Не могу да пронађем одељак.",
- "nosuchsectiontext": "Ð\9fокÑ\83Ñ\88али Ñ\81Ñ\82е да Ñ\83Ñ\80едиÑ\82е одеÑ\99ак коÑ\98и не поÑ\81Ñ\82оÑ\98и.\nÐ\9cожда Ñ\98е пÑ\80емеÑ\88Ñ\82ен или обрисан док сте прегледали страницу.",
+ "nosuchsectiontext": "Ð\9fокÑ\83Ñ\88али Ñ\81Ñ\82е да Ñ\83Ñ\80едиÑ\82е одеÑ\99ак коÑ\98и не поÑ\81Ñ\82оÑ\98и.\nÐ\9cожда Ñ\98е пÑ\80емеÑ\88Ñ\82ен или избрисан док сте прегледали страницу.",
"loginreqtitle": "Потребна је пријава",
"loginreqlink": "пријављени",
"loginreqpagetext": "Морате бити $1 да бисте видели друге странице.",
"anontalkpagetext": "----\n<em>Ово је страница за разговор с анонимним корисником који још нема налог или га не користи.</em>\nЗбог тога морамо да користимо бројчану IP адресу како бисмо га препознали.\nТакву адресу може делити више корисника.\nАко сте анонимни корисник и мислите да су вам упућене примедбе, [[Special:CreateAccount|отворите налог]] или се [[Special:UserLogin|пријавите]] да бисте избегли будућу забуну с осталим анонимним корисницима.",
"noarticletext": "На овој страници тренутно нема текста.\nМожете [[Special:Search/{{PAGENAME}}|потражити овај наслов]] на другим страницама,\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражити сродне извештаје] или [{{fullurl:{{FULLPAGENAME}}|action=edit}} направити ову страницу]</span>.",
"noarticletext-nopermission": "Тренутно нема текста на овој страници.\nМожете да [[Special:Search/{{PAGENAME}}|потражите овај наслов странице]] на другим страницама или <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} претражите сродне евиденције]</span>, али немате дозволу да направите ову страницу.",
- "missing-revision": "Ревизија бр. $1 на страници под именом „{{FULLPAGENAME}}“ не постоји.\n\nОво се обично дешава када пратите застарелу везу до странице која је обрисана.\nВише информација можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
+ "missing-revision": "Ревизија бр. $1 на страници под именом „{{FULLPAGENAME}}“ не постоји.\n\nОво се обично дешава када пратите застарели линк до странице која је избрисана.\nВише информација можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
"userpage-userdoesnotexist": "Кориснички налог „<nowiki>$1</nowiki>“ није отворен.\nРазмислите да ли заиста желите да направите/уредите ову страницу.",
"userpage-userdoesnotexist-view": "Кориснички налог „$1“ није отворен.",
"blocked-notice-logextract": "Овај корисник је тренутно блокиран.\nПоследњи унос у евиденцији блокирања је наведен испод као референца:",
"previewconflict": "Овај преглед осликава како ће изгледати текст у текстуалном оквиру.",
"session_fail_preview": "Извињавамо се! Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\nМожда сте одјављени. <strong>Проверите да ли сте пријављени и покушајте поново</strong>.\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите, те проверите да ли су на вашем претраживачу дозвољени колачићи са овог сајта.",
"session_fail_preview_html": "Нисмо могли да обрадимо вашу измену због губитка података сесије.\n\n<em>Будући да је на овом викију омогућен унос HTML ознака, преглед је сакривен као мера предострожности против напада преко јаваскрипта.</em>\n\n<strong>Ако сте покушали да направите праву измену, покушајте поново.<strong>\nАко и даље не ради, покушајте да се [[Special:UserLogout|одјавите]] и поново пријавите и проверите да ли ваш прегледач дозвољава колачиће са овог сајта.",
- "token_suffix_mismatch": "<strong>Ð\92аÑ\88а измена Ñ\98е одбаÑ\87ена Ñ\98еÑ\80 Ñ\98е ваÑ\88 пÑ\80егледаÑ\87 Ñ\83баÑ\86ио знакове инÑ\82еÑ\80пÑ\83нкÑ\86иÑ\98е Ñ\83 новÑ\87иÑ\9b Ñ\83Ñ\80еÑ\92иваÑ\9aа.</strong>\nТо Ñ\81е понекад догаÑ\92а када Ñ\81е коÑ\80иÑ\81Ñ\82и неиÑ\81пÑ\80аван поÑ\81Ñ\80едник.",
+ "token_suffix_mismatch": "<strong>Ð\92аÑ\88а измена Ñ\98е одбиÑ\98ена Ñ\98еÑ\80 Ñ\98е ваÑ\88 клиÑ\98енÑ\82 Ñ\83баÑ\86ио знакове инÑ\82еÑ\80пÑ\83нкÑ\86иÑ\98е Ñ\83 Ñ\82окен Ñ\83Ñ\80еÑ\92иваÑ\9aа.</strong>\nÐ\98змена Ñ\98е одбиÑ\98ена Ñ\80ади Ñ\81пÑ\80еÑ\87аваÑ\9aа Ñ\83ниÑ\88Ñ\82аваÑ\9aа Ñ\82екÑ\81Ñ\82а Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eво Ñ\81е понекад догаÑ\92а када коÑ\80иÑ\81Ñ\82иÑ\82е пÑ\80облемаÑ\82иÑ\87ни анонимни поÑ\81Ñ\80едник коÑ\98и Ñ\98е заÑ\81нован на вебÑ\83.",
"edit_form_incomplete": "<strong>Неки делови обрасца за уређивање нису стигли до сервера. Проверите да ли су ваше измене непромењене и покушајте поново.</strong>",
"editing": "Уређујете $1",
"creating": "Прављење странице $1",
"editingold": "<strong>Упозорење: уређујете застарелу ревизију ове странице.</strong>\nАко је сачувате, све промене направљене од ове ревизије ће бити изгубљене.",
"unicode-support-fail": "Ваш прегледач не подржава Unicode. Он је неопоходан за уређивање страница, па зато не могу сачувати измену.",
"yourdiff": "Разлике",
- "copyrightwarning": "Имајте на уму да се сви доприноси на овом викију сматрају као објављени под лиценцом $2 (више на $1).\nАко не желите да се ваши текстови мењају и размењују без ограничења, онда их не шаљите овде.<br />\nИсто тако обећавате да сте Ви аутор текста, или да сте га умножили с извора који је у јавном власништву.\n<strong>Не шаљите радове заштићене ауторским правима без дозволе!</strong>",
+ "copyrightwarning": "Имајте на уму да се сви доприноси на овом викију сматрају као објављени под лиценцом $2 (више на $1).\nАко не желите да се ваши текстови мењају и размењују без ограничења, онда их не шаљите овде.<br />\nИсто тако обећавате да сте Ви аутор текста, или да сте га умножили са извора који је у јавном власништву.\n<strong>Не шаљите радове заштићене ауторским правима без дозволе!</strong>",
"copyrightwarning2": "Имајте на уму да се сви доприноси на овом викију могу мењати, враћати или брисати од других корисника.\nАко не желите да се ваши текстови слободно мењају и расподељују, не шаљите их овде.<br />\nИсто тако обећавате да сте ви аутор текста, или да сте га умножили с извора који је у јавном власништву (више на $1).\n<strong>Не шаљите радове заштићене ауторским правима без дозволе!</strong>",
"editpage-cannot-use-custom-model": "Модел садржаја ове странице се не може променити.",
"longpageerror": "<strong>Грешка: текст који сте унели је величине {{PLURAL:$1|један килобајт|$1 килобајта}}, што је веће од {{PLURAL:$2|дозвољеног једног килобајта|дозвољена $2 килобајта|дозвољених $2 килобајта}}.</strong>\nСтраница не може бити сачувана.",
"readonlywarning": "<strong>Упозорење: база података је закључана ради одржавања, тако да тренутно нећете моћи да сачувате измене.</strong>\nМожда бисте желели сачувати текст за касније у некој текстуалној датотеци.\n\nСистемски администратор је навео следеће објашњење: $1",
"protectedpagewarning": "<strong>Упозорење: Ова страница је заштићена, тако да само корисници са администраторским овлашћењима могу да је уређују.</strong>\nНајновији унос у евиденцији је наведен испод као референца:",
"semiprotectedpagewarning": "<strong>Напомена:</strong> Ова страница је заштићена, тако да само аутоматски потврђени корисници могу да је уређују.\nНајновији унос у евиденцији је наведен испод као референца:",
- "cascadeprotectedwarning": "<strong>Упозорење:</strong> Ова страница је заштићена, тако да само корисници са [[Special:ListGroupRights|одређеним правима]] могу да је уређују, јер је она укључена у {{PLURAL:$1|следећу страницу која је заштићена|следеће странице које су заштићене}} преносивом заштитом:",
+ "cascadeprotectedwarning": "<strong>Упозорење:</strong> Ова страница је заштићена тако да само корисници са [[Special:ListGroupRights|одређеним правима]] могу да је уређују, јер је укључена у {{PLURAL:$1|следећу страницу која је заштићена|следеће странице које су заштићене}} преносивом заштитом:",
"titleprotectedwarning": "<strong>Упозорење: Ова страница је заштићена, тако да су потребна [[Special:ListGroupRights|посебна права]] да се она направи.</strong>\nНајновији унос у евиденцији је наведен испод као референца:",
"templatesused": "{{PLURAL:$1|Шаблон који се користи|Шаблони који се користе}} на овој страници:",
"templatesusedpreview": "{{PLURAL:$1|Шаблон|Шаблони}} у овом претпрегледу:",
"permissionserrorstext": "Немате дозволу за ову радњу из {{PLURAL:$1|следећег|следећих}} разлога:",
"permissionserrorstext-withaction": "Немате дозволу да $2 из {{PLURAL:$1|следећег|следећих}} разлога:",
"contentmodelediterror": "Не можете уредити ову ревизију јер је њен модел садржаја <code>$1</code>, што се разликује од актуелног модела садржаја странице <code>$2</code>.",
- "recreate-moveddeleted-warn": "<strong>УпозоÑ\80еÑ\9aе: поново пÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а Ñ\98е пÑ\80еÑ\82Ñ\85одно обÑ\80иÑ\81ана.</strong>\n\nРазмоÑ\82Ñ\80иÑ\82е да ли Ñ\98е пÑ\80икладно да наÑ\81Ñ\82авиÑ\82е Ñ\81 Ñ\83Ñ\80еÑ\92иваÑ\9aем ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eвде Ñ\98е наведена евиденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81 образложењем:",
- "moveddeleted-notice": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обрисана.\nЕвиденција брисања, заштите и премештања странице је наведена испод као референца.",
- "moveddeleted-notice-recent": "Ð\96ао нам Ñ\98е, ова Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е недавно обÑ\80иÑ\81ана (Ñ\83 поÑ\81ледÑ\9aиÑ\85 24 Ñ\81аÑ\82а).\nÐ\95виденÑ\86иÑ\98а Ñ\9aеног бÑ\80иÑ\81аÑ\9aа, заÑ\88Ñ\82иÑ\82е и пÑ\80емеÑ\88Ñ\82аÑ\9aа налази Ñ\81е иÑ\81под:",
+ "recreate-moveddeleted-warn": "<strong>УпозоÑ\80еÑ\9aе: Ð\9fоново пÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а Ñ\98е пÑ\80еÑ\82Ñ\85одно избÑ\80иÑ\81ана.</strong>\n\nРазмоÑ\82Ñ\80иÑ\82е да ли Ñ\98е пÑ\80икладно да наÑ\81Ñ\82авиÑ\82е Ñ\81а Ñ\83Ñ\80еÑ\92иваÑ\9aем ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eвде Ñ\98е наведена евиденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81а образложењем:",
+ "moveddeleted-notice": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избрисана.\nЕвиденција брисања, заштите и премештања странице је наведена испод као референца.",
+ "moveddeleted-notice-recent": "Ð\9dажалоÑ\81Ñ\82, ова Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е недавно избÑ\80иÑ\81ана (Ñ\83 поÑ\81ледÑ\9aиÑ\85 24 Ñ\81аÑ\82а).\nÐ\95виденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа, заÑ\88Ñ\82иÑ\82е и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е наведена Ñ\98е иÑ\81под као Ñ\80еÑ\84еÑ\80енÑ\86а:",
"log-fulllog": "Погледај целу евиденцију",
"edit-hook-aborted": "Измену је прекинула кука.\nНије дато никакво образложење.",
- "edit-gone-missing": "Ð\9dе могÑ\83 да ажÑ\83Ñ\80иÑ\80ам Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83.\nÐ\98згледа да Ñ\98е обрисана.",
+ "edit-gone-missing": "Ð\9dе могÑ\83 да ажÑ\83Ñ\80иÑ\80ам Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83.\nÐ\98згледа да Ñ\98е избрисана.",
"edit-conflict": "Сукоб измена.",
"edit-no-change": "Ваша измена је занемарена јер није било никаквих промена у тексту.",
"postedit-confirmation-created": "Страница је направљена.",
"edit-already-exists": "Не могу да направим страницу.\nИзгледа да она већ постоји.",
"defaultmessagetext": "Подразумевани текст поруке",
"content-failed-to-parse": "Не могу да рашчланим садржај типа $2 за модел $1: $3",
- "invalid-content-data": "Ð\9dеиÑ\81пÑ\80авни подаци садржаја",
+ "invalid-content-data": "Ð\9dеважеÑ\9bи подаци садржаја",
"content-not-allowed-here": "Садржај модела „$1“ није дозвољен на страници [[$2]]",
"editwarning-warning": "Ако напустите ову страницу, изгубићете све измене које сте направили. Ако сте пријављени, можете онемогућити ово упозорење у својим подешавањима, у одељку „{{int:prefs-editing}}“.",
"editpage-invalidcontentmodel-title": "Модел садржаја није подржан",
"converter-manual-rule-error": "Пронађена је грешка у правилу за ручно претварање језика",
"undo-success": "Измена се може поништити.\nПроверите разлике испод, па сачувајте измене.",
"undo-failure": "Ова измена се не може поништити због сукоба измена.",
- "undo-norev": "Ð\9dе могÑ\83 да вÑ\80аÑ\82им изменÑ\83 Ñ\98еÑ\80 не поÑ\81Ñ\82оÑ\98и или Ñ\98е обрисана.",
+ "undo-norev": "Ð\9dе могÑ\83 да вÑ\80аÑ\82им изменÑ\83 Ñ\98еÑ\80 не поÑ\81Ñ\82оÑ\98и или Ñ\98е избрисана.",
"undo-nochange": "Изгледа да је измена већ поништена.",
"undo-summary": "Поништена ревизија $1 {{GENDER:$2|корисника|кориснице}} [[Special:Contributions/$2|$2]] ([[User talk:$2|разговор]])",
"undo-summary-username-hidden": "Поништи измену $1 скривеног корисника",
"last": "разл",
"page_first": "прва",
"page_last": "последња",
- "histlegend": "Избор разлика: означите кутијице ревизија за упоређивање и притисните enter или дугме на дну.<br />\nОбјашњење: <strong>({{int:cur}})</strong> = разлика с актуелном ревизијом, <strong>({{int:last}})</strong> = разлика с претходном ревизијом, <strong>{{int:minoreditletter}}</strong> = мања измена",
+ "histlegend": "Избор разлика: означите кутијице ревизија за упоређивање и притисните enter или дугме на дну.<br />\nОбјашњење: <strong>({{int:cur}})</strong> = разлика са актуелном ревизијом, <strong>({{int:last}})</strong> = разлика са претходном ревизијом, <strong>{{int:minoreditletter}}</strong> = мања измена",
"history-fieldset-title": "Претрага измена",
- "history-show-deleted": "Само обрисане ревизије",
+ "history-show-deleted": "Само избрисане ревизије",
"histfirst": "најстарије",
"histlast": "најновије",
"historysize": "({{PLURAL:$1|1 бајт|$1 бајта|$1 бајтова}})",
"history-feed-title": "Историја ревизија",
"history-feed-description": "Историја измена ове странице на викију",
"history-feed-item-nocomment": "$1 у $2",
- "history-feed-empty": "ТÑ\80ажена Ñ\81Ñ\82Ñ\80аниÑ\86а не поÑ\81Ñ\82оÑ\98и.\nÐ\9cогÑ\83Ñ\9bе да Ñ\98е обÑ\80иÑ\81ана Ñ\81 викиÑ\98а или Ñ\98е пÑ\80еименована.\nÐ\9fокÑ\83Ñ\88аÑ\98Ñ\82е да [[Special:Search|пÑ\80еÑ\82Ñ\80ажиÑ\82е вики]] за Ñ\81лиÑ\87не странице.",
+ "history-feed-empty": "ТÑ\80ажена Ñ\81Ñ\82Ñ\80аниÑ\86а не поÑ\81Ñ\82оÑ\98и.\nÐ\9cогÑ\83Ñ\9bе да Ñ\98е избÑ\80иÑ\81ана Ñ\81а викиÑ\98а или Ñ\98е пÑ\80еименована.\nÐ\9fокÑ\83Ñ\88аÑ\98Ñ\82е да [[Special:Search|пÑ\80еÑ\82Ñ\80ажиÑ\82е вики]] за Ñ\80елеванÑ\82не нове странице.",
"history-edit-tags": "Уреди ознаке изабраних ревизија",
"rev-deleted-comment": "(опис измене уклоњен)",
"rev-deleted-user": "(корисничко име уклоњено)",
"rev-delundel": "промени видљивост",
"rev-showdeleted": "прикажи",
"revisiondelete": "Брисање/враћање ревизија",
- "revdelete-nooldid-title": "Ð\9dема Ñ\82Ñ\80ажене измене",
+ "revdelete-nooldid-title": "Ð\9dеважеÑ\9bа одÑ\80едиÑ\88на Ñ\80евизиÑ\98а",
"revdelete-nooldid-text": "Нисте навели одредишну ревизију на којој треба да се изврши ова функција, та ревизија не постоји, или покушавате да сакријете актуелну ревизију.",
"revdelete-no-file": "Тражена датотека не постоји.",
- "revdelete-show-file-confirm": "Ð\88еÑ\81Ñ\82е ли Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да видиÑ\82е обрисану ревизију датотеке „<nowiki>$1</nowiki>“ од $2; $3?",
+ "revdelete-show-file-confirm": "Ð\88еÑ\81Ñ\82е ли Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да видиÑ\82е избрисану ревизију датотеке „<nowiki>$1</nowiki>“ од $2; $3?",
"revdelete-show-file-submit": "Да",
"revdelete-selected-text": "{{PLURAL:$1|Изабрана ревизија|Изабране ревизије|Изабраних ревизија}} [[:$2]]:",
"revdelete-selected-file": "{{PLURAL:$1|Изабрана верзија датотеке|Изабране верзије датотеке}} [[:$2]]:",
"logdelete-selected": "{{PLURAL:$1|Изабрана ставка у историји|Изабране ставке у историји}}:",
"revdelete-text-text": "Избрисане ревизије ће и даље бити видљиве у историји странице, али делови њиховог садржаја неће бити јавно доступни.",
"revdelete-text-file": "Избрисане верзије датотеке ће и даље бити видљиве у историји датотеке, али делови њиховог садржаја неће бити јавно доступни.",
- "logdelete-text": "Ð\9eбрисани догађаји у евиденцијама ће се идаље појављивати у евиденцији, али ће делови њиховог садржаја бити недоступни јавности.",
+ "logdelete-text": "Ð\98збрисани догађаји у евиденцијама ће се идаље појављивати у евиденцији, али ће делови њиховог садржаја бити недоступни јавности.",
"revdelete-text-others": "Остали администратори ће и даље моћи да приступе скривеном садржају и врате га, осим ако се поставе додатна ограничења.",
- "revdelete-confirm": "Потврдите да намеравате ово урадити, да разумете последице и да то чините у складу с [[{{MediaWiki:Policy-url}}|правилима]].",
+ "revdelete-confirm": "Потврдите да намеравате ово урадити, да разумете последице и да то чините у складу са [[{{MediaWiki:Policy-url}}|правилима]].",
"revdelete-suppress-text": "Сакривање измена би требало користити <strong>само</strong> у следећим случајевима:\n* злонамерни или погрдни подаци\n* неприкладни лични подаци\n*: <em>кућна адреса и број телефона, број кредитне картице, ЈМБГ итд.</em>",
"revdelete-legend": "Ограничења видљивости",
"revdelete-hide-text": "Текст ревизије",
"logdelete-failure": "'''Не могу да поставим видљивост историје:'''\n$1",
"revdel-restore": "промени видљивост",
"pagehist": "Историја странице",
- "deletedhist": "Ð\9eбрисана историја",
+ "deletedhist": "Ð\98збрисана историја",
"revdelete-hide-current": "Грешка при сакривању ставке од $1, $2: ово је актуелна ревизија.\nНе може да буде сакривена.",
"revdelete-show-no-access": "Грешка при приказивању ставке од $1, $2: означена је као „ограничена“.\nНемате приступ до ње.",
"revdelete-modify-no-access": "Грешка при мењању ставке од $1, $2: означена је као „ограничена“.\nНемате приступ до ње.",
"mergehistory-from": "Изворна страница:",
"mergehistory-into": "Одредишна страница:",
"mergehistory-list": "Спојива историја измена",
- "mergehistory-merge": "СледеÑ\9bе Ñ\80евизиÑ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е [[:$1]] могÑ\83 Ñ\81е Ñ\81поÑ\98иÑ\82и Ñ\81а [[:$2]].\nÐ\9aоÑ\80иÑ\81Ñ\82иÑ\82е дÑ\83гмиÑ\9bе Ñ\83 колони да биÑ\81Ñ\82е Ñ\81поÑ\98или Ñ\80евизиÑ\98е коÑ\98е Ñ\81Ñ\83 напÑ\80авÑ\99ене пÑ\80е наведеног вÑ\80емена.\nÐ\9aоÑ\80иÑ\88Ñ\9bеÑ\9aе навигаÑ\86иониÑ\85 Ð²ÐµÐ·а ће поништити ову колону.",
+ "mergehistory-merge": "СледеÑ\9bе Ñ\80евизиÑ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е [[:$1]] могÑ\83 Ñ\81е Ñ\81поÑ\98иÑ\82и Ñ\81а [[:$2]].\nÐ\9aоÑ\80иÑ\81Ñ\82иÑ\82е дÑ\83гмиÑ\9bе Ñ\83 колони да биÑ\81Ñ\82е Ñ\81поÑ\98или Ñ\80евизиÑ\98е коÑ\98е Ñ\81Ñ\83 напÑ\80авÑ\99ене пÑ\80е наведеног вÑ\80емена.\nÐ\9aоÑ\80иÑ\88Ñ\9bеÑ\9aе навигаÑ\86иониÑ\85 Ð»Ð¸Ð½ÐºÐ¾Ð²а ће поништити ову колону.",
"mergehistory-go": "Прикажи измене које се могу спојити",
"mergehistory-submit": "Споји ревизије",
"mergehistory-empty": "Нема измена за спајање.",
"mergehistory-done": "$3 {{PLURAL:$3|ревизија странице $1 је спојена|ревизије странице $1 су спојене|ревизија странице $1 је спојено}} у [[:$2]].",
"mergehistory-fail": "Не могу да спојим историје. Проверите страницу и временске параметре.",
- "mergehistory-fail-bad-timestamp": "Временска ознака није валидна.",
+ "mergehistory-fail-bad-timestamp": "Временска ознака је неважећа.",
"mergehistory-fail-invalid-source": "Изворна страница није валидна.",
- "mergehistory-fail-invalid-dest": "Одредишна страница није валидна.",
+ "mergehistory-fail-invalid-dest": "Одредишна страница је неважећа.",
"mergehistory-fail-no-change": "Спајање историје није спојило ниједну ревизију. Проверите параметре странице и времена.",
"mergehistory-fail-permission": "Немате овлашћење за спајање историје.",
"mergehistory-fail-self-merge": "Изворна и одредишна страница не могу бити исте.",
"diff-multi-manyusers": "({{PLURAL:$1|Није приказана међуизмена|Нису приказане $1 међуизмене|Није приказано $1 међуизмена}} од више од $2 корисника)",
"diff-paragraph-moved-tonew": "Пасус је премештен. Кликните да пређете на његово ново место.",
"diff-paragraph-moved-toold": "Пасус је премештен. Кликните да пређете на његово старо место.",
- "difference-missing-revision": "{{PLURAL:$2|Ð\88една Ñ\80евизиÑ\98а|$2 Ñ\80евизиÑ\98е}} од ове Ñ\80азлике ($1) не {{PLURAL:$2|поÑ\81Ñ\82оÑ\98и|поÑ\81Ñ\82оÑ\98е}}.\n\nÐ\9eво Ñ\81е обиÑ\87но деÑ\88ава када пÑ\80аÑ\82иÑ\82е заÑ\81Ñ\82аÑ\80елÑ\83 везÑ\83 до Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98а Ñ\98е обрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
+ "difference-missing-revision": "{{PLURAL:$2|Ð\88една Ñ\80евизиÑ\98а|$2 Ñ\80евизиÑ\98е}} ове Ñ\80азлике ($1) не {{PLURAL:$2|поÑ\81Ñ\82оÑ\98и|поÑ\81Ñ\82оÑ\98е}}.\n\nÐ\9eво Ñ\81е обиÑ\87но деÑ\88ава када пÑ\80аÑ\82иÑ\82е заÑ\81Ñ\82аÑ\80ели линк до Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98а Ñ\98е избрисана.\nДетаље можете да пронађете у [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} евиденцији брисања].",
"searchresults": "Резултати претраге",
"search-filter-title-prefix-reset": "Претражи све странице",
"searchresults-title": "Резултати претраге за „$1“",
"prefs-watchlist-managetokens": "Управљај жетонима",
"prefs-misc": "Друга подешавања",
"prefs-resetpass": "промени лозинку",
- "prefs-changeemail": "промени или уклони имејл адресу",
- "prefs-setemail": "постави имејл адресу",
+ "prefs-changeemail": "промени или уклони имејл-адресу",
+ "prefs-setemail": "постави имејл-адресу",
"prefs-email": "Опције имејла",
"prefs-rendering": "Изглед",
"saveprefs": "Сачувај",
"restoreprefs": "Врати сва подешавања на подразумеване вредности (у свим одељцима)",
"prefs-editing": "Уређивање",
"searchresultshead": "Претрага",
- "stub-threshold": "Ð\9fÑ\80аг за обликоваÑ\9aе везе као клице ($1):",
+ "stub-threshold": "Ð\9fÑ\80аг за обликоваÑ\9aе линкова као клице ($1):",
"stub-threshold-sample-link": "пример",
"stub-threshold-disabled": "онемогућено",
"recentchangesdays": "Број дана у скорашњим изменама:",
"email": "Имејл",
"prefs-help-realname": "Право име је опционално.\nАко је наведено, биће коришћено за приписивање вашег рада.",
"prefs-help-email": "Имејл адреса је опционална, али је потребна за ресетовање лозинке, ако је заборавите.",
- "prefs-help-email-others": "ТакоÑ\92е можеÑ\82е изабÑ\80аÑ\82и да допÑ\83Ñ\81Ñ\82иÑ\82е дÑ\80Ñ\83гима да ваÑ\81 конÑ\82акÑ\82иÑ\80аÑ\98Ñ\83 пÑ\80еко имеÑ\98ла пÑ\83Ñ\82ем везе на вашој корисничкој страници или страници за разговор.\nВаша имејл адреса неће бити приказана другим корисницима који вас контактирају.",
- "prefs-help-email-required": "Ð\9fоÑ\82Ñ\80ебна Ñ\98е имеÑ\98л адÑ\80еÑ\81а.",
+ "prefs-help-email-others": "ТакоÑ\92е можеÑ\82е изабÑ\80аÑ\82и да допÑ\83Ñ\81Ñ\82иÑ\82е дÑ\80Ñ\83гима да ваÑ\81 конÑ\82акÑ\82иÑ\80аÑ\98Ñ\83 пÑ\80еко имеÑ\98ла пÑ\83Ñ\82ем линка на вашој корисничкој страници или страници за разговор.\nВаша имејл адреса неће бити приказана другим корисницима који вас контактирају.",
+ "prefs-help-email-required": "Ð\98меÑ\98л-адÑ\80еÑ\81а Ñ\98е неопÑ\85одна.",
"prefs-info": "Основне информације",
"prefs-i18n": "Интернационализација",
"prefs-signature": "Потпис",
"userrights-expiry-existing": "Постојеће време истека: $3, $2",
"userrights-expiry-othertime": "Друго време:",
"userrights-expiry-options": "1 дан:1 day,1 недеља:1 week,1 месец:1 month,3 месеца:3 months,6 месеци:6 months,1 година:1 year",
- "userrights-invalid-expiry": "Време истицања групе „$1“ није валидно.",
+ "userrights-invalid-expiry": "Време истицања групе „$1“ је неважеће.",
"userrights-expiry-in-past": "Време истицања групе „$1“ је прошло.",
"userrights-cannot-shorten-expiry": "Не можете убрзати истек чланства у групи „$1”. Само корисници са дозволом да додају или уклоне ову групу могу да убрзају рок истека.",
"userrights-conflict": "Сукоб промена корисничких права! Прегледајте и проверите ваше промене.",
"right-autocreateaccount": "Пријавите се аутоматски са екстерним корисничким налогом",
"right-minoredit": "означавање измена мањим",
"right-move": "премештање страница",
- "right-move-subpages": "премештање страница с њиховим подстраницама",
+ "right-move-subpages": "премештање страница са њиховим подстраницама",
"right-move-rootuserpages": "премештање основних корисничких страница",
"right-move-categorypages": "премештање категорија",
"right-movefile": "премештање датотека",
"right-apihighlimits": "коришћење виших граница за упите из API-ја",
"right-writeapi": "коришћење API-ја за писање",
"right-delete": "брисање страница",
- "right-bigdelete": "брисање страница с великом историјом",
+ "right-bigdelete": "брисање страница са великом историјом",
"right-deletelogentry": "брисање и враћање одређених уноса у евиденцији",
"right-deleterevision": "брисање и враћање одређених ревизија страница",
- "right-deletedhistory": "пÑ\80егледаÑ\9aе обрисаних ставки историје без повезаног текста",
- "right-deletedtext": "пÑ\80егледаÑ\9aе обÑ\80иÑ\81аног Ñ\82екÑ\81Ñ\82а и пÑ\80омена измеÑ\92Ñ\83 обрисаних ревизија",
- "right-browsearchive": "пÑ\80еÑ\82Ñ\80ага обрисаних страница",
- "right-undelete": "вÑ\80аÑ\9bаÑ\9aе обрисаних страница",
+ "right-deletedhistory": "пÑ\80егледаÑ\9aе избрисаних ставки историје без повезаног текста",
+ "right-deletedtext": "пÑ\80егледаÑ\9aе избÑ\80иÑ\81аног Ñ\82екÑ\81Ñ\82а и пÑ\80омена измеÑ\92Ñ\83 избрисаних ревизија",
+ "right-browsearchive": "пÑ\80еÑ\82Ñ\80ага избрисаних страница",
+ "right-undelete": "вÑ\80аÑ\9bаÑ\9aе избрисаних страница",
"right-suppressrevision": "прегледање, скривање и враћање одређених ревизија страница од свих корисника",
"right-viewsuppressed": "прегледање измена скривених од свих корисника",
"right-suppressionlog": "прегледање приватних евиденција",
"right-editmyuserjs": "уређивање сопствених JavaScript датотека",
"right-viewmywatchlist": "преглед сопственог списка надгледања",
"right-editmywatchlist": "уређивање сопственог списка надгледања; неке предузете радње ће свеједно додати странице на списак и без овог права",
- "right-viewmyprivateinfo": "пÑ\80еглед Ñ\81воÑ\98иÑ\85 лиÑ\87ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л адресу, право име)",
- "right-editmyprivateinfo": "Ñ\83Ñ\80еÑ\92иваÑ\9aе Ñ\81опÑ\81Ñ\82вениÑ\85 лиÑ\87ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л адресе, правог имена)",
+ "right-viewmyprivateinfo": "пÑ\80еглед Ñ\81воÑ\98иÑ\85 пÑ\80иваÑ\82ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л-адресу, право име)",
+ "right-editmyprivateinfo": "Ñ\83Ñ\80еÑ\92иваÑ\9aе Ñ\81опÑ\81Ñ\82вениÑ\85 пÑ\80иваÑ\82ниÑ\85 подаÑ\82ака (нпÑ\80. имеÑ\98л-адресе, правог имена)",
"right-editmyoptions": "уређивање сопствених подешавања",
"right-rollback": "брзо враћање измена последњег корисника који је мењао одређену страницу",
"right-markbotedits": "означавање враћених измена као измене бота",
"right-userrights": "уређивање свих корисничких права",
"right-userrights-interwiki": "уређивање корисничких права на другим викијима",
"right-siteadmin": "закључавање и откључавање базе података",
- "right-override-export-depth": "извоз Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и повазене Ñ\81Ñ\82Ñ\80аниÑ\86е до дÑ\83бине од пеÑ\82 веза",
+ "right-override-export-depth": "извоз Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и повазене Ñ\81Ñ\82Ñ\80аниÑ\86е до дÑ\83бине од пеÑ\82 линкова",
"right-sendemail": "слање имејла другим корисницима",
"right-managechangetags": "прављење и (де)активирање [[Special:Tags|ознака]]",
"right-applychangetags": "примењивање [[Special:Tags|ознака]] на нечије промене",
"grant-uploadeditmovefile": "Отпремање, замена и премештање датотека",
"grant-uploadfile": "Отпремање нових датотека",
"grant-basic": "Основна права",
- "grant-viewdeleted": "Ð\9fÑ\80еглед обрисаних страница и датотека",
+ "grant-viewdeleted": "Ð\9fÑ\80еглед избрисаних страница и датотека",
"grant-viewmywatchlist": "Преглед вашег списак надгледања",
"grant-viewrestrictedlogs": "Прегледање ограничених уноса у евиденцији",
"newuserlogpage": "Евиденција нових корисника",
"action-reupload-shared": "премостите ову датотеку са заједничког складишта",
"action-upload_by_url": "отпремите ову датотеку путем УРЛ-а",
"action-writeapi": "користите API за писање",
- "action-delete": "обришете ову страницу",
+ "action-delete": "избришете ову страницу",
"action-deleterevision": "бришете ревизије",
"action-deletelogentry": "бришете уносе у евиденцијама",
- "action-deletedhistory": "пÑ\80егледаÑ\82е обрисану историју странице",
- "action-deletedtext": "пÑ\80егледаÑ\82е обрисани текст ревизије",
- "action-browsearchive": "пÑ\80еÑ\82Ñ\80ажÑ\83Ñ\98еÑ\82е обрисане странице",
+ "action-deletedhistory": "пÑ\80егледаÑ\82е избрисану историју странице",
+ "action-deletedtext": "пÑ\80егледаÑ\82е избрисани текст ревизије",
+ "action-browsearchive": "пÑ\80еÑ\82Ñ\80ажÑ\83Ñ\98еÑ\82е избрисане странице",
"action-undelete": "враћате странице",
"action-suppressrevision": "прегледате и враћате сакривене ревизије",
"action-suppressionlog": "прегледате ову приватну евиденције",
"rcfilters-savedqueries-apply-label": "Направи филтер",
"rcfilters-savedqueries-apply-and-setdefault-label": "Направи подразумевани филтер",
"rcfilters-savedqueries-cancel-label": "Откажи",
- "rcfilters-savedqueries-add-new-title": "Сачувајте актуелна подешавања филтера",
+ "rcfilters-savedqueries-add-new-title": "Сачувајте тренутна подешавања филтера",
"rcfilters-savedqueries-already-saved": "Ови филтери су већ сачувани. Промените своја подешавања да бисте направили нове сачуване филтере.",
"rcfilters-restore-default-filters": "Врати подразумеване филтере",
"rcfilters-clear-all-filters": "Уклоните све филтере",
"rcfilters-show-new-changes": "Најновије промене",
"rcfilters-search-placeholder": "Филтрирајте промене (користите мени или претрагу за име филтера)",
- "rcfilters-invalid-filter": "Ð\9dеиÑ\81пÑ\80аван филтер",
+ "rcfilters-invalid-filter": "Ð\9dеважеÑ\9bи филтер",
"rcfilters-empty-filter": "Нема активних филтера. Сви доприноси су приказани.",
"rcfilters-filterlist-title": "Филтери",
"rcfilters-filterlist-whatsthis": "Како ово функционише?",
"rcfilters-filter-lastrevision-label": "Последња измена",
"rcfilters-filter-lastrevision-description": "Само најновија промена на страници.",
"rcfilters-filter-previousrevision-label": "Није последња ревизија",
- "rcfilters-filter-previousrevision-description": "Све пÑ\80омене коÑ\98е ниÑ\81Ñ\83 â\80\9eпоÑ\81ледÑ\9aе Ñ\80евизиÑ\98еâ\80\9c.",
- "rcfilters-filter-excluded": "Изостављено",
+ "rcfilters-filter-previousrevision-description": "Све пÑ\80омене коÑ\98е ниÑ\81Ñ\83 â\80\9eпоÑ\81ледÑ\9aе Ñ\80евизиÑ\98еâ\80\9d.",
+ "rcfilters-filter-excluded": "Изузето",
"rcfilters-tag-prefix-namespace-inverted": "<strong>:није</strong> $1",
- "rcfilters-exclude-button-off": "Изостави означено",
- "rcfilters-exclude-button-on": "Изостави одабрано",
+ "rcfilters-exclude-button-off": "Изузми изабрано",
+ "rcfilters-exclude-button-on": "Изузми изабрано",
"rcfilters-view-tags": "Означене измене",
"rcfilters-view-namespaces-tooltip": "Филтрирајте резултате према именском простору",
"rcfilters-view-tags-tooltip": "Филтрирајте резултате према ознаци измене",
"rc-old-title": "првобитно направљено као „$1“",
"recentchangeslinked": "Сродне промене",
"recentchangeslinked-feed": "Сродне измене",
- "recentchangeslinked-toolbox": "СÑ\80одне измене",
+ "recentchangeslinked-toolbox": "СÑ\80одне пÑ\80омене",
"recentchangeslinked-title": "Измене сродне са „$1“",
"recentchangeslinked-summary": "Унесите име странице да бисте видели промене на страницама које су повезане са или са те странице. (Да бисте видели чланове категорије, унесите {{ns:category}}:Име категорије). Промене на страницама које су на [[Special:Watchlist|Вашем списку надгледања]] су <strong>подебљане</strong>.",
"recentchangeslinked-page": "Назив странице:",
"upload_directory_missing": "Фасцикла за слање ($1) недостаје и сервер је не може направити.",
"upload_directory_read_only": "Сервер не може да пише по фасцикли за слање ($1).",
"uploaderror": "Грешка при отпремању",
- "upload-recreate-warning": "<strong>УпозоÑ\80еÑ\9aе: даÑ\82оÑ\82ека Ñ\81 Ñ\82им називом Ñ\98е обÑ\80иÑ\81ана или пÑ\80емеÑ\88Ñ\82ена.</strong>\n\nÐ\95виденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81е налази иÑ\81под:",
+ "upload-recreate-warning": "<strong>УпозоÑ\80еÑ\9aе: Ð\94аÑ\82оÑ\82ека Ñ\81а Ñ\82им именом Ñ\98е избÑ\80иÑ\81ана или пÑ\80емеÑ\88Ñ\82ена.</strong>\n\nÐ\95виденÑ\86иÑ\98а бÑ\80иÑ\81аÑ\9aа и пÑ\80емеÑ\88Ñ\82аÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е наведена Ñ\98е иÑ\81под Ñ\81а обÑ\80азложеÑ\9aем:",
"uploadtext": "Користите образац испод да бисте отпремили датотеке.\nЗа преглед или претрагу постојећих датотека, погледајте [[Special:FileList|списак отпремљених датотека]], поновна отпремања су наведена у [[Special:Log/upload|евиденцији отпремања]], а брисања у [[Special:Log/delete|евиденцији брисања]].\n\nДатотеку додајете на жељену страницу користећи следеће обрасце:\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Слика.jpg]]</nowiki></code>''' за верзију слике у пуној величини\n* '''<code><nowiki>[[</nowiki>{{ns:file}}<nowiki>:Слика.png|200п|мини|лево|опис]]</nowiki></code>''' за верзију слике с величином од 200 пиксела која је приказана у засебном оквиру, заједно с описом.\n* '''<code><nowiki>[[</nowiki>{{ns:media}}<nowiki>:Датотека.ogg]]</nowiki></code>''' за директно повезивање с датотеком без њеног приказивања",
"upload-permitted": "Дозвољени {{PLURAL:$2|тип|типови}} датотека: $1.",
"upload-preferred": "Препоручени {{PLURAL:$2|тип|типови}} датотека: $1.",
"ignorewarning": "Занемари упозорења и сачувај датотеку",
"ignorewarnings": "Занемари сва упозорења",
"minlength1": "Назив датотеке мора имати барем један знак.",
- "illegalfilename": "Ð\94аÑ\82оÑ\82ека â\80\9e$1â\80\9c Ñ\81адÑ\80жи знакове коÑ\98и ниÑ\81Ñ\83 дозвоÑ\99ени Ñ\83 називима Ñ\81Ñ\82Ñ\80аниÑ\86а.\nÐ\9fÑ\80омениÑ\82е назив даÑ\82оÑ\82еке и поново Ñ\98е поÑ\88аÑ\99ите.",
+ "illegalfilename": "Ð\98ме даÑ\82оÑ\82еке â\80\9e$1â\80\9c Ñ\81адÑ\80жи знакове коÑ\98и ниÑ\81Ñ\83 дозвоÑ\99ени Ñ\83 наÑ\81ловима Ñ\81Ñ\82Ñ\80аниÑ\86а.\nÐ\9fÑ\80еименÑ\83Ñ\98Ñ\82е даÑ\82оÑ\82екÑ\83 и покÑ\83Ñ\88аÑ\82е да Ñ\98е поново оÑ\82пÑ\80емите.",
"filename-toolong": "Називи датотека могу имати највише 240 бајтова.",
"badfilename": "Име датотеке је промењено у „$1“.",
"filetype-mime-mismatch": "Проширење датотеке „.$1“ не одговара препознатом типу MIME датотеке ($2).",
"large-file": "Препоручљиво је да датотеке не буду веће од $1; ова датотека је $2.",
"largefileserver": "Ова датотека прелази ограничење величине.",
"emptyfile": "Датотека коју сте послали је празна.\nУзрок може бити грешка у називу датотеке.\nПроверите да ли заиста желите да је пошаљете.",
- "windows-nonascii-filename": "Ð\9eваÑ\98 вики не подÑ\80жава називе даÑ\82оÑ\82ека Ñ\81 посебним знацима.",
+ "windows-nonascii-filename": "Ð\9eваÑ\98 вики не подÑ\80жава имена даÑ\82оÑ\82ека Ñ\81а посебним знацима.",
"fileexists": "Датотека с овим именом већ постоји. Погледајте <strong>[[:$1]]</strong> ако нисте сигурни да ли желите да је промените.\n[[$1|thumb]]",
"filepageexists": "Страница с описом ове датотеке је већ направљена овде <strong>[[:$1]]</strong>, иако датотека не постоји.\nОпис који сте навели се неће појавити на страници с описом.\nДа би се ваш опис овде нашао, потребно је да га ручно измените.\n[[$1|thumb]]",
"fileexists-extension": "Датотека са сличним називом већ постоји: [[$2|thumb]]\n* Назив датотеке коју шаљете: <strong>[[:$1]]</strong>\n* Назив постојеће датотеке: <strong>[[:$2]]</strong>\nДа ли желите да користите препознатљивије име?",
"fileexists-thumbnail-yes": "Изгледа да је датотека умањено издање слике ''(thumbnail)''.\n[[$1|thumb]]\nПроверите датотеку <strong>[[:$1]]</strong>.\nАко је проверена датотека иста слика оригиналне величине, није потребно слати додатну слику.",
"file-thumbnail-no": "Датотека почиње са <strong>$1</strong>.\nИзгледа да се ради о умањеној слици ''(thumbnail)''.\nУколико имате ову слику у пуној величини, пошаљите је, а ако немате, промените назив датотеке.",
"fileexists-forbidden": "Датотека с овим називом већ постоји и не може се заменити.\nАко и даље желите да пошаљете датотеку, вратите се и изаберите други назив.\n[[File:$1|thumb|center|$1]]",
- "fileexists-shared-forbidden": "Датотека с овим називом већ постоји у заједничкој остави.\nВратите се и пошаљите датотеку с другим називом.\n[[File:$1|thumb|center|$1]]",
+ "fileexists-shared-forbidden": "Датотека са овим именом већ постоји у заједничкој остави.\nАко још увек желите да отпремите датотеку, вратите се и користите ново име.\n[[File:$1|thumb|center|$1]]",
"fileexists-no-change": "Датотека је дупликат актуелне верзије <strong>[[:$1]]</strong>.",
"fileexists-duplicate-version": "Датотека је дупликат {{PLURAL:$2|старе верзије|старих верзија}} <strong>[[:$1]]</strong>.",
"file-exists-duplicate": "Ово је дупликат {{PLURAL:$1|следеће датотеке|следећих датотека}}:",
- "file-deleted-duplicate": "Ð\94аÑ\82оÑ\82ека иÑ\81Ñ\82овеÑ\82на овоÑ\98 ([[:$1]]) Ñ\98е пÑ\80еÑ\82Ñ\85одно обÑ\80иÑ\81ана.\nÐ\9fогледаÑ\98Ñ\82е иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа пÑ\80е поновног Ñ\81лаÑ\9aа.",
- "file-deleted-duplicate-notitle": "Ð\94аÑ\82оÑ\82ека иденÑ\82иÑ\87на овоÑ\98 пÑ\80еÑ\82Ñ\85одно Ñ\98е обÑ\80иÑ\81ана и име Ñ\98оÑ\98 Ñ\98е Ñ\81акÑ\80ивено.\nТÑ\80ебали биÑ\81Ñ\82е пиÑ\82аÑ\82и некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.",
+ "file-deleted-duplicate": "Ð\94аÑ\82оÑ\82ека коÑ\98а Ñ\98е иденÑ\82иÑ\87на овоÑ\98 ([[:$1]]) Ñ\98е Ñ\80аниÑ\98е била избÑ\80иÑ\81ана.\nТÑ\80ебаÑ\82е да пÑ\80овеÑ\80иÑ\82е иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа Ñ\82е даÑ\82оÑ\82еке пÑ\80е него Ñ\88Ñ\82о наÑ\81Ñ\82авиÑ\82е Ñ\81а Ñ\9aеним поновним опÑ\82Ñ\80емаÑ\9aем.",
+ "file-deleted-duplicate-notitle": "Ð\94аÑ\82оÑ\82ека коÑ\98а Ñ\98е иденÑ\82иÑ\87на овоÑ\98 Ñ\80аниÑ\98е Ñ\98е избÑ\80иÑ\81ана и име Ñ\98оÑ\98 Ñ\98е Ñ\81акÑ\80ивено.\nТÑ\80ебаÑ\82е да пиÑ\82аÑ\82е некога ко може видети податке скривених датотека да прегледа ситуацију пре него што поново отпремите датотеку.",
"uploadwarning": "Упозорење при отпремању",
"uploadwarning-text": "Измените опис датотеке и покушајте поново.",
"uploadwarning-text-nostash": "Ре-отпремите датотеку, измените опис испод и покушајте поново.",
"upload-description": "Опис датотеке",
"upload-options": "Опције отпремања",
"watchthisupload": "Надгледај ову датотеку",
- "filewasdeleted": "Датотека с овим називом је раније послата, али је обрисана.\nПроверите $1 пре него што наставите с поновним слањем.",
+ "filewasdeleted": "Датотека са овим именом је раније оптремљена и након тога избрисана.\nТребате да проверите $1 пре него што наставите са њеним поновним оптремањем.",
"filename-bad-prefix": "Назив датотеке коју шаљете почиње са <strong>„$1“</strong>, а њега обично додељују дигитални фотоапарати.\nИзаберите назив датотеке који описује њен садржај.",
"filename-prefix-blacklist": " #<!-- оставите овај ред онаквим какав јесте --> <pre>\n# Синтакса је следећа:\n# * Све од тарабе па до краја реда је коментар\n# * Сваки ред означава префикс типичних назива датотека које додељивају дигитални апарати\nCIMG # Касио\nDSC_ # Никон\nDSCF # Фуџи\nDSCN # Никон\nDUW # неки мобилни телефони\nIMG # опште\nJD # Џеноптик\nMGP # Пентакс\nPICT # разно\n #</pre> <!-- оставите овај ред онаквим какав јесте -->",
- "upload-proto-error": "Ð\9dеиÑ\81пÑ\80аван протокол",
+ "upload-proto-error": "Ð\9dеважеÑ\9bи протокол",
"upload-proto-error-text": "Слање са спољне локације захтева адресу која почиње са <code>http://</code> или <code>ftp://</code>.",
"upload-file-error": "Унутрашња грешка",
"upload-file-error-text": "Дошло је до унутрашње грешке при отварању привремене датотеке на серверу.\nКонтактирајте [[Special:ListUsers/sysop|администратора]].",
"backend-fail-hashes": "Не могу да добијем дисперзије датотеке за упоређивање.",
"backend-fail-notsame": "Већ постоји неистоветна датотека – $1.",
"backend-fail-invalidpath": "$1 није важећа путања за складиштење.",
- "backend-fail-delete": "Ð\9dе могÑ\83 да обришем датотеку „$1”.",
+ "backend-fail-delete": "Ð\9dе могÑ\83 да избришем датотеку „$1”.",
"backend-fail-describe": "Не могу да променим метаподатке за датотеку „$1“.",
"backend-fail-alreadyexists": "Датотека $1 већ постоји.",
"backend-fail-store": "Не могу да сместим датотеку $1 у $2.",
"filejournal-fail-dbquery": "Не могу да ажурирам новинарску базу за складишну основу „$1“.",
"lockmanager-notlocked": "Не могу да откључам „$1“ јер није закључан.",
"lockmanager-fail-closelock": "Не могу да затворим катанац за „$1“.",
- "lockmanager-fail-deletelock": "Ð\9dе могÑ\83 да обришем катанац за „$1“.",
+ "lockmanager-fail-deletelock": "Ð\9dе могÑ\83 да избришем катанац за „$1“.",
"lockmanager-fail-acquirelock": "Не могу да се закључам за „$1“.",
"lockmanager-fail-openlock": "Не могу да отворим катанац за „$1“. Уверите се да је ваш директоријум за отпремање исправно конфигурисан и да ваш веб-сервер има дозволу да пише у том директоријуму. Погледајте https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:$wgUploadDirectory за више информација.",
"lockmanager-fail-releaselock": "Не могу да ослободим катанац за „$1“.",
"uploadstash-no-such-key": "Нема таквог кључа ($1). Не могу уклонити.",
"uploadstash-no-extension": "Додатак је празан.",
"uploadstash-zero-length": "Датотека је празна",
- "invalid-chunk-offset": "Ð\9dеиÑ\81пÑ\80авна полазна тачка",
+ "invalid-chunk-offset": "Ð\9dеважеÑ\9bа полазна тачка",
"img-auth-accessdenied": "Приступ је одбијен",
"img-auth-nopathinfo": "Недостаје PATH_INFO.\nВаш сервер није подешен да прослеђује овакве податке.\nМожда је заснован на CGI-ју који не подржава img_auth.\nПогледајте https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization?uselang=sr-ec.",
- "img-auth-notindir": "Ð\97аÑ\85Ñ\82евана пÑ\83Ñ\82аÑ\9aа ниÑ\98е Ñ\83 подеÑ\88еноÑ\98 Ñ\84аÑ\81Ñ\86икли за отпремање.",
- "img-auth-badtitle": "Не могу да направим валидан наслов за „$1“.",
+ "img-auth-notindir": "ТÑ\80ажена пÑ\83Ñ\82аÑ\9aа ниÑ\98е Ñ\83 подеÑ\88еном диÑ\80екÑ\82оÑ\80иÑ\98Ñ\83мÑ\83 за отпремање.",
+ "img-auth-badtitle": "Не могу да саставим важећи наслов из „$1“.",
"img-auth-nologinnWL": "Нисте пријављени и „$1” није на списку дозвољених.",
"img-auth-nofile": "Датотека „$1“ не постоји.",
"img-auth-isdir": "Покушавате да приступите фасцикли „$1“.\nДозвољен је само приступ датотекама.",
"img-auth-streaming": "Учитавам „$1“...",
"img-auth-public": "Сврха img_auth.php је да прослеђује датотеке из приватних викија.\nОвај вики је постављен као јавни.\nРади сигурности, img_auth.php је онемогућен.",
"img-auth-noread": "Корисник нема приступ за читање „$1“.",
- "http-invalid-url": "Ð\9dеиÑ\81пÑ\80авна адÑ\80еÑ\81а: $1",
+ "http-invalid-url": "Ð\9dеважеÑ\9bи URL: $1",
"http-invalid-scheme": "Адресе са шемом „$1“ нису подржане.",
"http-request-error": "HTTP захтев није прошао због непознате грешке.",
"http-read-error": "HTTP грешка при читању.",
"licenses-edit": "Уреди избор лиценци",
"license-nopreview": "(преглед није доступан)",
"upload_source_url": "(ваша изабрана датотека од важећих, јавно доступних адреса)",
- "upload_source_file": "(ваша одабрана датотека са вашег рачунара)",
- "listfiles-delete": "обриши",
+ "upload_source_file": "(ваша одабрана датотека са рачунара)",
+ "listfiles-delete": "избриши",
"listfiles-summary": "Ова посебна страница приказује све отпремљене датотеке.",
"listfiles_search_for": "Назив датотеке:",
"listfiles-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"file-anchor-link": "Датотека",
"filehist": "Историја датотеке",
"filehist-help": "Кликните на датум/време да бисте видели тадашњу верзију датотеке.",
- "filehist-deleteall": "обриши све",
- "filehist-deleteone": "обриши",
+ "filehist-deleteall": "избриши све",
+ "filehist-deleteone": "избриши",
"filehist-revert": "врати",
"filehist-current": "актуелна",
"filehist-datetime": "Датум/време",
"filehist-comment": "Коментар",
"imagelinks": "Употреба датотеке",
"linkstoimage": "{{PLURAL:$1|Следећа страница користи|$1 следеће странице користе|$1 следећих страница користи}} ову датотеку:",
- "linkstoimage-more": "Ð\92иÑ\88е од $1 {{PLURAL:$1|Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\80иÑ\81Ñ\82и|Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\80иÑ\81Ñ\82е|Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\80иÑ\81Ñ\82е}} овÑ\83 даÑ\82оÑ\82екÑ\83.\nСледеÑ\9bи Ñ\81пиÑ\81ак пÑ\80иказÑ\83Ñ\98е Ñ\81амо {{PLURAL:$1|пÑ\80вÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а коÑ\80иÑ\81Ñ\82и|пÑ\80ве $1 Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е коÑ\80иÑ\81Ñ\82е|пÑ\80виÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\98е коÑ\80иÑ\81Ñ\82е}} ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].",
+ "linkstoimage-more": "Ð\92иÑ\88е од $1 {{PLURAL:$1|Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\80иÑ\81Ñ\82и|Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\80иÑ\81Ñ\82е|Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\80иÑ\81Ñ\82и}} овÑ\83 даÑ\82оÑ\82екÑ\83.\nСледеÑ\9bи Ñ\81пиÑ\81ак пÑ\80иказÑ\83Ñ\98е {{PLURAL:$1|пÑ\80вÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 коÑ\98а коÑ\80иÑ\81Ñ\82и|пÑ\80ве $1 Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е коÑ\80иÑ\81Ñ\82е|пÑ\80виÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а коÑ\98е коÑ\80иÑ\81Ñ\82е}} Ñ\81амо ову датотеку.\nДоступан је и [[Special:WhatLinksHere/$2|потпуни списак]].",
"nolinkstoimage": "Нема страница које користе ову датотеку.",
- "morelinkstoimage": "Ð\9fогледаÑ\98Ñ\82е [[Special:WhatLinksHere/$1|виÑ\88е веза]] до ове датотеке.",
+ "morelinkstoimage": "Ð\9fогледаÑ\98Ñ\82е [[Special:WhatLinksHere/$1|виÑ\88е линкова]] до ове датотеке.",
"linkstoimage-redirect": "$1 (преусмерење датотеке) $2",
"duplicatesoffile": "{{PLURAL:$1|Следећа датотека је дупликат|Следеће $1 датотеке су дупликати|Следећих $1 датотека су дупликати}} ове датотеке ([[Special:FileDuplicateSearch/$2|детаљније]]):",
"sharedupload": "Ова датотека се налази на $1 и може се користити и на другим пројектима.",
"sharedupload-desc-edit": "Ова датотека се налази на $1 и може да се користи на другим пројектима.\nЊен опис можете да измените на [$2 одговарајућој страници].",
"sharedupload-desc-create": "Ова датотека се налази на $1 и може да се користи на другим пројектима.\nЊен опис можете да измените на [$2 одговарајућој страници].",
"filepage-nofile": "Не постоји датотека с овим називом.",
- "filepage-nofile-link": "Не постоји датотека с овим називом, али је можете [$1 послати].",
+ "filepage-nofile-link": "Не постоји датотека са овим именом, али је можете [$1 опремити].",
"uploadnewversion-linktext": "Отпреми нову верзију ове датотеке",
"shared-repo-from": "из $1",
"shared-repo": "заједничко складиште",
"filerevert-success": "Датотека <strong>[[Media:$1|$1]]</strong> је враћена на [$4 верзију од $2; $3].",
"filerevert-badversion": "Не постоји ранија локална верзија ове датотеке са наведеном временском ознаком.",
"filerevert-identical": "Актуелна верзија датотеке је индентична изабраној.",
- "filedelete": "Ð\9eбÑ\80иÑ\88и $1",
- "filedelete-legend": "Ð\9eбриши датотеку",
+ "filedelete": "Ð\91Ñ\80иÑ\81аÑ\9aе даÑ\82оÑ\82еке/Ñ\81Ñ\82Ñ\80аниÑ\86е $1",
+ "filedelete-legend": "Ð\98збриши датотеку",
"filedelete-intro": "Бришете датотеку '''[[Media:$1|$1]]''' заједно с њеном историјом.",
"filedelete-intro-old": "Бришете верзију датотеке '''[[Media:$1|$1]]''' од [$4 $2; $3].",
"filedelete-comment": "Разлог:",
- "filedelete-submit": "Ð\9eбриши",
- "filedelete-success": "Датотека '''$1''' је обрисана.",
- "filedelete-success-old": "Ð\92еÑ\80зиÑ\98а даÑ\82оÑ\82еке <strong>[[Media:$1|$1]]</strong> од $2, $3 Ñ\98е обрисана.",
+ "filedelete-submit": "Ð\98збриши",
+ "filedelete-success": "Датотека <strong>$1</strong> је избрисана.",
+ "filedelete-success-old": "Ð\92еÑ\80зиÑ\98а даÑ\82оÑ\82еке <strong>[[Media:$1|$1]]</strong> од $2, $3 Ñ\98е избрисана.",
"filedelete-nofile": "Датотека '''$1''' не постоји.",
"filedelete-nofile-old": "Не постоји архивирана верзија датотеке <strong>$1</strong> са наведеним особинама.",
"filedelete-otherreason": "Други/додатни разлог:",
"filedelete-reason-dropdown": "*Најчешћи разлози брисања\n** Кршење ауторских права\n** Дупликати датотека",
"filedelete-edit-reasonlist": "Уреди разлоге брисања",
"filedelete-maintenance": "Брисање и враћање датотека је привремено онемогућено због одржавања.",
- "filedelete-maintenance-title": "Ð\9dе могÑ\83 да обришем датотеку",
+ "filedelete-maintenance-title": "Ð\9dе могÑ\83 да избришем датотеку",
"mimesearch": "MIME претрага",
"mimesearch-summary": "Ова страница омогућава филтрирање датотека према њиховим MIME типовима.\nУлазни подаци: contenttype/subtype или contenttype/*, нпр. <code>image/jpeg</code>.",
"mimetype": "MIME тип:",
"listduplicatedfiles-entry": "[[:File:$1|$1]] има [[$3|{{PLURAL:$2|један дупликат|$2 дупликата}}]].",
"unusedtemplates": "Некоришћени шаблони",
"unusedtemplatestext": "Ова страница наводи све странице у именском простору {{ns:template}} које нису укључене ни на једној другој страници.\nПре брисања проверите да ли друге странице воде до тих шаблона.",
- "unusedtemplateswlh": "оÑ\81Ñ\82але везе",
+ "unusedtemplateswlh": "оÑ\81Ñ\82али линкови",
"randompage": "Случајна страница",
"randompage-nopages": "Нема страница у {{PLURAL:$2|следећем именском простору|следећим именским просторима}}: $1.",
"randomincategory": "Случајна страница у категорији",
"statistics-header-users": "Корисници",
"statistics-header-hooks": "Остало",
"statistics-articles": "Странице са садржајем",
- "statistics-pages": "СÑ\82Ñ\80аниÑ\86а",
+ "statistics-pages": "СÑ\82Ñ\80аниÑ\86е",
"statistics-pages-desc": "Све странице на викију, укључујући странице за разговор, преусмерења итд.",
- "statistics-files": "Ð\91Ñ\80оÑ\98 поÑ\81лаÑ\82иÑ\85 даÑ\82оÑ\82ека",
+ "statistics-files": "Ð\9eÑ\82пÑ\80емÑ\99ене даÑ\82оÑ\82еке",
"statistics-edits": "Број измена страница откад постоји {{SITENAME}}",
"statistics-edits-average": "Просечан број измена по страници",
"statistics-users": "Регистровани корисници",
"pageswithprop-prophidden-long": "сакривено дуго текстуално својство ($1)",
"pageswithprop-prophidden-binary": "сакривено дуго бинарно својство ($1)",
"doubleredirects": "Двострука преусмерења",
- "doubleredirectstext": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а пÑ\80иказÑ\83Ñ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е пÑ\80еÑ\83Ñ\81меÑ\80аваÑ\98Ñ\83 на дÑ\80Ñ\83га пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nСваки Ñ\80ед Ñ\81адÑ\80жи везе према првом и другом преусмерењу, као и одредишну страницу другог преусмерења која је обично „прави“ чланак на кога прво преусмерење треба да упућује.\n<del>Прецртани</del> уноси су већ решени.",
+ "doubleredirectstext": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а пÑ\80иказÑ\83Ñ\98е Ñ\81Ñ\82Ñ\80аниÑ\86е коÑ\98е пÑ\80еÑ\83Ñ\81меÑ\80аваÑ\98Ñ\83 на дÑ\80Ñ\83га пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nСваки Ñ\80ед Ñ\81адÑ\80жи линкове према првом и другом преусмерењу, као и одредишну страницу другог преусмерења која је обично „прави“ чланак на кога прво преусмерење треба да упућује.\n<del>Прецртани</del> уноси су већ решени.",
"double-redirect-fixed-move": "[[$1]] је премештен.\nАутоматски је ажурирано и сада преусмерава на [[$2]].",
"double-redirect-fixed-maintenance": "Аутоматски исправља двострука преусмерења из [[$1]] у [[$2]] као део одржавања",
"double-redirect-fixer": "Исправљач преусмерења",
"brokenredirects": "Покварена преусмерења",
- "brokenredirectstext": "Следећа преусмерења упућују на непостојеће странице:",
+ "brokenredirectstext": "Следећа преусмерења воде на непостојеће странице:",
"brokenredirects-edit": "уреди",
- "brokenredirects-delete": "обриши",
- "withoutinterwiki": "СÑ\82Ñ\80аниÑ\86е без Ñ\98езиÑ\87киÑ\85 веза",
- "withoutinterwiki-summary": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е ниÑ\81Ñ\83 повезане Ñ\81 другим језицима.",
+ "brokenredirects-delete": "избриши",
+ "withoutinterwiki": "СÑ\82Ñ\80аниÑ\86е без Ñ\98езиÑ\87киÑ\85 линкова",
+ "withoutinterwiki-summary": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е немаÑ\98Ñ\83 линкове пÑ\80ема веÑ\80зиÑ\98ама на другим језицима.",
"withoutinterwiki-legend": "Префикс",
"withoutinterwiki-submit": "Прикажи",
"fewestrevisions": "Странице са најмање ревизија",
"nbytes": "$1 {{PLURAL:$1|бајт|бајта|бајтова}}",
"ncategories": "$1 {{PLURAL:$1|категорија|категорије|категорија}}",
"ninterwikis": "$1 {{PLURAL:$1|међувики|међувикија|међувикија}}",
- "nlinks": "$1 {{PLURAL:$1|веза|везе|веза}}",
+ "nlinks": "$1 {{PLURAL:$1|линк|линка|линкова}}",
"nmembers": "$1 {{PLURAL:$1|члан|члана|чланова}}",
"nmemberschanged": "$1 → $2 {{PLURAL:$2|члан|члана|чланова}}",
"nrevisions": "$1 {{PLURAL:$1|ревизија|ревизије|ревизија}}",
"unusedimages": "Некоришћене датотеке",
"wantedcategories": "Тражене категорије",
"wantedpages": "Тражене странице",
- "wantedpages-summary": "СпиÑ\81ак непоÑ\81Ñ\82оÑ\98еÑ\9bиÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\98виÑ\88е веза до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].",
- "wantedpages-badtitle": "Ð\9dеиÑ\81пÑ\80аван наÑ\81лов Ñ\83 Ñ\81еÑ\82у резултата: $1",
+ "wantedpages-summary": "СпиÑ\81ак непоÑ\81Ñ\82оÑ\98еÑ\9bиÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\81а наÑ\98виÑ\88е линкова до њих, на овом списку се не налазе странице до којих воде преусмерења. За списак покварених преусмерења погледајте [[{{#special:BrokenRedirects}}|списак покварених преусмерења]].",
+ "wantedpages-badtitle": "Ð\9dевалидан наÑ\81лов Ñ\83 Ñ\81кÑ\83пу резултата: $1",
"wantedfiles": "Тражене датотеке",
"wantedfiletext-cat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити <del>поништене</del> са списка. Поред тога, странице које садрже непостојеће датотеке се налазе [[:$1|овде]].",
"wantedfiletext-nocat": "Следеће датотеке се користе, али не постоје. Датотеке из других ризница могу бити наведене иако не постоје. Такве датотеке ће бити <del>поништене</del> са списка.",
"wantedfiletext-nocat-noforeign": "Следеће датотеке се користе, али не постоје.",
"wantedtemplates": "Тражени шаблони",
- "mostlinked": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е веза",
- "mostlinkedcategories": "Ð\9aаÑ\82егоÑ\80иÑ\98е Ñ\81а наÑ\98виÑ\88е веза",
- "mostlinkedtemplates": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е веза",
+ "mostlinked": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е линкова",
+ "mostlinkedcategories": "Ð\9aаÑ\82егоÑ\80иÑ\98е Ñ\81а наÑ\98виÑ\88е линкова",
+ "mostlinkedtemplates": "СÑ\82Ñ\80аниÑ\86е Ñ\81а наÑ\98виÑ\88е линкова",
"mostcategories": "Странице са највише категорија",
- "mostimages": "Ð\94аÑ\82оÑ\82еке Ñ\81а наÑ\98виÑ\88е веза",
+ "mostimages": "Ð\94аÑ\82оÑ\82еке Ñ\81а наÑ\98виÑ\88е линкова",
"mostinterwikis": "Странице са највише међувикија",
"mostrevisions": "Странице са највише ревизија",
"prefixindex": "Све странице са префиксом",
"shortpages": "Кратке странице",
"longpages": "Дугачке странице",
"deadendpages": "Ћорсокаци",
- "deadendpagestext": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е немаÑ\98Ñ\83 везе до других страница на овом викију.",
+ "deadendpagestext": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е немаÑ\98Ñ\83 линкове до других страница на овом викију.",
"protectedpages": "Заштићене странице",
"protectedpages-filters": "Филтери:",
"protectedpages-indef": "Само неограничене заштите",
"apisandbox-submit-invalid-fields-title": "Нека поља нису валидна",
"apisandbox-submit-invalid-fields-message": "Молимо Вас поправите означена поља и покушајте поново.",
"apisandbox-results": "Резултати",
- "apisandbox-sending-request": "СлаÑ\9aе API заÑ\85Ñ\82ева...",
+ "apisandbox-sending-request": "ШаÑ\99ем API заÑ\85Ñ\82евâ\80¦",
"apisandbox-loading-results": "Пријем API резултата...",
"apisandbox-results-error": "Дошло је до грешке приликом учитавања резултата API упита: $1.",
"apisandbox-request-selectformat-label": "Прикажи сахтеване податке као:",
"apisandbox-request-time": "Време за извршавање захтјева: {{PLURAL:$1|$1 милисекунда|$1 милисекунде|$1 милисекунди}}",
"apisandbox-results-fixtoken": "Исправи токен и пошаљи поново",
"apisandbox-results-fixtoken-fail": "Неуспело добијање „$1“ токена.",
- "apisandbox-alert-page": "Поља на страници су неисправна.",
- "apisandbox-alert-field": "Вредност овог поља је неисправна.",
+ "apisandbox-alert-page": "Поља на страници нису важећа.",
+ "apisandbox-alert-field": "Вредност овог поља није важећа.",
"apisandbox-continue": "Настави",
"apisandbox-continue-clear": "Очисти",
"apisandbox-param-limit": "Унесите <kbd>max</kbd> да би сте користили највеће ограничење.",
"booksources-search-legend": "Претражи штампане изворе",
"booksources-isbn": "ISBN:",
"booksources-search": "Претражи",
- "booksources-text": "Ð\98Ñ\81под Ñ\81е налази Ñ\81пиÑ\81ак веза ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
+ "booksources-text": "Ð\98Ñ\81под Ñ\81е налази Ñ\81пиÑ\81ак линкова ка сајтовима који се баве продајом нових и половних књига, а који би могли имати додатне податке о књигама које тражите:",
"booksources-invalid-isbn": "Наведени ISBN број није валидан. Проверите да није дошло до грешке при копирању из првобитног извора.",
- "magiclink-tracking-rfc": "Странице с магичним RFC везама",
- "magiclink-tracking-pmid": "Странице с магичним PMID везама",
- "magiclink-tracking-isbn": "СÑ\82Ñ\80аниÑ\86е Ñ\81а ISBN магиÑ\87ним везама",
+ "magiclink-tracking-rfc": "Странице са магичним RFC линковима",
+ "magiclink-tracking-pmid": "Странице са магичним PMID линковима",
+ "magiclink-tracking-isbn": "СÑ\82Ñ\80аниÑ\86е Ñ\81а ISBN магиÑ\87ним линковима",
"specialloguserlabel": "Извршилац:",
"speciallogtitlelabel": "Циљ (наслов или {{ns:user}}:корисничко име):",
"log": "Евиденције",
"logeventslist-submit": "Прикажи",
- "logeventslist-more-filters": "Ð\9fÑ\80иказ додаÑ\82ниÑ\85 дневника:",
+ "logeventslist-more-filters": "Ð\9fÑ\80иказ додаÑ\82ниÑ\85 евиденÑ\86иÑ\98а:",
"logeventslist-patrol-log": "Евиденција патролирања",
"logeventslist-tag-log": "Евиденција ознака",
"all-logs-page": "Све јавне евиденције",
"categories-submit": "Прикажи",
"categoriespagetext": "{{PLURAL:$1|1=Следећа категорија садржи|Следеће категорије садрже}} странице или датотеке.\n[[Special:UnusedCategories|Некоришћене категорије]] нису приказане овде.\nПогледајте и [[Special:WantedCategories|тражене категорије]].",
"categoriesfrom": "Прикажи категорије почев од:",
- "deletedcontributions": "Ð\9eбрисани кориснички доприноси",
- "deletedcontributions-title": "Ð\9eбрисани кориснички доприноси",
+ "deletedcontributions": "Ð\98збрисани кориснички доприноси",
+ "deletedcontributions-title": "Ð\98збрисани кориснички доприноси",
"sp-deletedcontributions-contribs": "доприноси",
- "linksearch": "Ð\9fÑ\80еÑ\82Ñ\80ага Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 веза",
+ "linksearch": "Ð\9fÑ\80еÑ\82Ñ\80ага Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 линкова",
"linksearch-pat": "Образац претраге:",
"linksearch-ns": "Именски простор:",
"linksearch-ok": "Претражи",
"linksearch-text": "Могу се користити џокери попут „*.wikipedia.org“.\nПотребан је највиши домен, као „*.org“.<br />\n{{PLURAL:$2|1=Подржан протокол|Подржани протоколи}}: $1 (задаје http:// ако не наведете протокол).",
- "linksearch-line": "$1 веза Ñ\83 $2",
+ "linksearch-line": "$1 води Ñ\81а $2",
"linksearch-error": "Џокери се могу појавити само на почетку адресе.",
"listusersfrom": "Прикажи кориснике почев од:",
"listusers-submit": "Прикажи",
"restricted-displaytitle-ignored": "Странице са занемареним насловима за приказ",
"noindex-category-desc": "Странице које у себи имају магичну реч <code><nowiki>__NOINDEX__</nowiki></code>.",
"index-category-desc": "Странице које у себи имају магичну реч <code><nowiki>__INDEX__</nowiki></code> и самим тим су индексиране од стране робота.",
- "broken-file-category-desc": "СÑ\82Ñ\80аниÑ\86е коÑ\98е имаÑ\98Ñ\83 везе до непоÑ\81Ñ\82оÑ\98еÑ\9bиÑ\85 даÑ\82оÑ\82ека.",
+ "broken-file-category-desc": "СÑ\82Ñ\80аниÑ\86а Ñ\81адÑ\80жи покваÑ\80ени линк до даÑ\82оÑ\82еке (линк за Ñ\83гÑ\80аÑ\92иваÑ\9aе даÑ\82оÑ\82еке када она не поÑ\81Ñ\82оÑ\98и).",
"hidden-category-category-desc": "Категорије које у себи имају магичну реч <code><nowiki>__HIDDENCAT__</nowiki></code> и самим тим се не приказују у одељку за категорије на страницама.",
"trackingcategories-nodesc": "Опис није доступан.",
"trackingcategories-disabled": "Категорија је онемогућена",
"defemailsubject": "{{SITENAME}} — Имејл од {{GENDER:$1|корисника|кориснице}} „$1”",
"usermaildisabled": "Кориснички имејл је онемогућен",
"usermaildisabledtext": "Не можете да шаљете имејлове другим корисницима на овом викију",
- "noemailtitle": "Нема имејл адресе",
- "noemailtext": "Ð\9eваÑ\98 коÑ\80иÑ\81ник ниÑ\98е навео валиднÑ\83 имеÑ\98л адресу.",
+ "noemailtitle": "Нема имејл-адресе",
+ "noemailtext": "Ð\9eваÑ\98 коÑ\80иÑ\81ник ниÑ\98е навео важеÑ\9bÑ\83 имеÑ\98л-адресу.",
"nowikiemailtext": "Овај корисник је одлучио да не прима имејлове од других корисника.",
- "emailnotarget": "Ð\9dепоÑ\81Ñ\82оÑ\98еÑ\9bе или неиÑ\81пÑ\80авно корисничко име примаоца.",
+ "emailnotarget": "Ð\9dепоÑ\81Ñ\82оÑ\98еÑ\9bе или наважеÑ\9bе корисничко име примаоца.",
"emailtarget": "Унос корисничког имена примаоца",
"emailusername": "Корисничко име:",
"emailusernamesubmit": "Пошаљи",
"unwatch": "Прекини надгледање",
"unwatchthispage": "Прекини надгледање",
"notanarticle": "Није страница са садржајем",
- "notvisiblerev": "Ð\9fоÑ\81ледÑ\9aа Ñ\80евизиÑ\98а дÑ\80Ñ\83гог коÑ\80иÑ\81ника Ñ\98е обрисана.",
+ "notvisiblerev": "Ð\9fоÑ\81ледÑ\9aа Ñ\80евизиÑ\98а дÑ\80Ñ\83гог коÑ\80иÑ\81ника Ñ\98е избрисана.",
"watchlist-details": "Имате {{PLURAL:$1|$1 страницу|$1 странице|$1 страница}} на свом списку надгледања (плус странице за разговор).",
"wlheader-enotif": "Обавештење имејлом је омогућено.",
"wlheader-showupdated": "Странице које су промењене откад сте их последњи пут посетили су <strong>подебљане</strong>.",
"enotif_minoredit": "Ово је мања измена",
"created": "направљена",
"changed": "измењена",
- "deletepage": "Ð\9eбриши страницу",
+ "deletepage": "Ð\98збриши страницу",
"confirm": "Потврди",
"excontent": "садржај је био: „$1“",
"excontentauthor": "садржај је био: „$1“, а једини уредник „[[Special:Contributions/$2|$2]]“ ([[User talk:$2|разговор]])",
"exbeforeblank": "садржај пре брисања је био: „$1“",
"delete-confirm": "Брисање странице „$1“",
"delete-legend": "Брисање",
- "historywarning": "<strong>Упозорење:</strong> страница коју желите да обришете има историју са $1 {{PLURAL:$1|ревизијом|ревизије|ревизија}}:",
+ "historywarning": "<strong>Упозорење:</strong> Страница коју желите да избришете има историју са $1 {{PLURAL:$1|ревизијом|ревизије|ревизија}}:",
"historyaction-submit": "Прикажи",
- "confirmdeletetext": "УпÑ\80аво Ñ\9bеÑ\82е обÑ\80иÑ\81аÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и Ñ\9aенÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83.\nÐ\9fоÑ\82вÑ\80диÑ\82е Ñ\81воÑ\98Ñ\83 намеÑ\80Ñ\83, да Ñ\80азÑ\83меÑ\82е поÑ\81ледиÑ\86е и да ово Ñ\80адиÑ\82е Ñ\83 Ñ\81кладÑ\83 Ñ\81 [[{{MediaWiki:Policy-url}}|правилима]].",
+ "confirmdeletetext": "УпÑ\80аво Ñ\9bеÑ\82е избÑ\80иÑ\81аÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, Ñ\83кÑ\99Ñ\83Ñ\87Ñ\83Ñ\98Ñ\83Ñ\9bи и Ñ\9aенÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83.\nÐ\9fоÑ\82вÑ\80диÑ\82е Ñ\81воÑ\98Ñ\83 намеÑ\80Ñ\83, да Ñ\80азÑ\83меÑ\82е поÑ\81ледиÑ\86е и да ово Ñ\80адиÑ\82е Ñ\83 Ñ\81кладÑ\83 Ñ\81а [[{{MediaWiki:Policy-url}}|правилима]].",
"actioncomplete": "Радња је завршена",
"actionfailed": "Радња није успела",
- "deletedtext": "СÑ\82Ñ\80аниÑ\86а â\80\9e$1â\80\9c Ñ\98е обÑ\80иÑ\81ана.\nÐ\9fогледаÑ\98Ñ\82е ''$2'' за запис недавних брисања.",
+ "deletedtext": "СÑ\82Ñ\80аниÑ\86а â\80\9e$1â\80\9c Ñ\98е избÑ\80иÑ\81ана.\nÐ\9fогледаÑ\98Ñ\82е $2 за запис недавних брисања.",
"dellogpage": "Евиденција брисања",
"dellogpagetext": "Испод је списак недавних брисања.",
"deletionlog": "евиденција брисања",
"delete-edit-reasonlist": "Уреди разлоге брисања",
"delete-toobig": "Ова страница има велику историју измена, преко $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}.\nБрисање таквих страница је ограничено да би се спречило случајно оптерећење сервера.",
"delete-warning-toobig": "Ова страница има велику историју измена, преко $1 {{PLURAL:$1|ревизија|ревизије|ревизија}}.\nЊено брисање може да поремети базу података, стога поступајте с опрезом.",
- "deleteprotected": "Ð\9dе можеÑ\82е обÑ\80иÑ\81аÑ\82и овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 заÑ\82о Ñ\88Ñ\82о је заштићена.",
+ "deleteprotected": "Ð\9dе можеÑ\82е да избÑ\80иÑ\88еÑ\82е овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 Ñ\98еÑ\80 је заштићена.",
"deleting-backlinks-warning": "<strong>Упозорење:</strong> бришете страницу која је укључена у [[Special:WhatLinksHere/{{FULLPAGENAME}}|друге странице]] или друге странице воде на њу.",
- "deleting-subpages-warning": "<strong>Ð\9fажÑ\9aа:</strong> СÑ\82Ñ\80аниÑ\86Ñ\83 коÑ\98Ñ\83 желиÑ\82е обрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].",
+ "deleting-subpages-warning": "<strong>УпозоÑ\80еÑ\9aе:</strong> СÑ\82Ñ\80аниÑ\86а коÑ\98Ñ\83 желиÑ\82е избрисати има [[Special:PrefixIndex/{{FULLPAGENAME}}/|{{PLURAL:$1|подстраницу|$1 подстранице|$1 подстраница|51=преко 50 подстраница}}]].",
"rollback": "Врати измене",
"rollbacklink": "врати",
"rollbacklinkcount": "врати $1 {{PLURAL:$1|измену|измене|измена}}",
"protect-legend": "Подешавања заштите",
"protectcomment": "Разлог:",
"protectexpiry": "Истиче:",
- "protect_expiry_invalid": "Време истека није важеће.",
+ "protect_expiry_invalid": "Време истека је неважеће.",
"protect_expiry_old": "Време истека је у прошлости.",
"protect-unchain-permissions": "Откључај даљња подешавања заштите",
"protect-text": "Овде можете да погледате и промените ниво заштите странице <strong>$1</strong>.",
"restriction-level-sysop": "потпуно заштићено",
"restriction-level-autoconfirmed": "полузаштићено",
"restriction-level-all": "сви нивои",
- "undelete": "Ð\9fÑ\80еглед обрисаних страница",
- "undeletepage": "Ð\9fÑ\80еглед и вÑ\80аÑ\9bаÑ\9aе обрисаних страница",
- "undeletepagetitle": "<strong>СледеÑ\9bи Ñ\81адÑ\80жаÑ\98 Ñ\81е Ñ\81аÑ\81Ñ\82оÑ\98и од обрисаних ревизија странице [[:$1|$1]]</strong>.",
- "viewdeletedpage": "Ð\9fÑ\80иказ обрисаних страница",
- "undeletepagetext": "{{PLURAL:$1|СледеÑ\9bа Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обÑ\80иÑ\81ана, али Ñ\98е Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и може биÑ\82и вÑ\80аÑ\9bена|СледеÑ\9bе $1 Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81Ñ\83 обÑ\80иÑ\81ане, али Ñ\81Ñ\83 Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и могÑ\83 биÑ\82и вÑ\80аÑ\9bене|СледеÑ\9bиÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обрисано, али су још у архиви и могу бити враћене}}.\nАрхива се повремено чисти од оваквих страница.",
+ "undelete": "Ð\9fÑ\80еглед избрисаних страница",
+ "undeletepage": "Ð\9fÑ\80еглед и вÑ\80аÑ\9bаÑ\9aе избрисаних страница",
+ "undeletepagetitle": "<strong>СледеÑ\9bи Ñ\81адÑ\80жаÑ\98 Ñ\81е Ñ\81аÑ\81Ñ\82оÑ\98и од избрисаних ревизија странице [[:$1|$1]]</strong>.",
+ "viewdeletedpage": "Ð\9fÑ\80еглед избрисаних страница",
+ "undeletepagetext": "{{PLURAL:$1|СледеÑ\9bа Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избÑ\80иÑ\81ана, али Ñ\98е Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и може биÑ\82и вÑ\80аÑ\9bена|СледеÑ\9bе $1 Ñ\81Ñ\82Ñ\80аниÑ\86е Ñ\81Ñ\83 избÑ\80иÑ\81ане, али Ñ\81Ñ\83 Ñ\98оÑ\88 Ñ\83 аÑ\80Ñ\85иви и могÑ\83 биÑ\82и вÑ\80аÑ\9bене|СледеÑ\9bиÑ\85 $1 Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избрисано, али су још у архиви и могу бити враћене}}.\nАрхива се повремено чисти од оваквих страница.",
"undelete-fieldset-title": "Враћање ревизија",
"undeleteextrahelp": "Да бисте вратили целу историју странице, оставите све кућице неозначене и кликните на дугме <strong><em>{{int:undeletebtn}}</em></strong>.\nАко желите да вратите одређене ревизије, означите их и кликните на <strong><em>{{int:undeletebtn}}</em></strong>.",
- "undeleterevisions": "{{PLURAL:$1|Ð\98змена}} обÑ\80иÑ\81ано: $1",
+ "undeleterevisions": "{{PLURAL:$1|Ð\98збÑ\80иÑ\81ана Ñ\98е|Ð\98збÑ\80иÑ\81ане Ñ\81Ñ\83|Ð\98збÑ\80иÑ\81ано Ñ\98е}} $1 {{PLURAL:$1|Ñ\80евизиÑ\98а|Ñ\80евизиÑ\98е|Ñ\80евизиÑ\98а}}",
"undeletehistory": "Ако вратите страницу, све ревизије ће бити враћене њеној историји.\nАко је у међувремену направљена нова страница с истим називом, враћене ревизије ће се појавити у њеној ранијој историји.",
- "undeleterevdel": "Ð\92Ñ\80аÑ\9bаÑ\9aе неÑ\9bе биÑ\82и извÑ\80Ñ\88ено ако Ñ\98е Ñ\80езÑ\83лÑ\82аÑ\82 Ñ\82ога делимиÑ\87но бÑ\80иÑ\81аÑ\9aе поÑ\81ледÑ\9aе Ñ\80евизиÑ\98е.\nУ Ñ\82аквим Ñ\81лÑ\83Ñ\87аÑ\98евима моÑ\80аÑ\82е иÑ\81кÑ\99Ñ\83Ñ\87иÑ\82и или оÑ\82кÑ\80иÑ\82и наÑ\98новиÑ\98е обрисане ревизије.",
- "undeletehistorynoadmin": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обÑ\80иÑ\81ана.\nРазлог за бÑ\80иÑ\81аÑ\9aе Ñ\81е налази иÑ\81под, заÑ\98едно Ñ\81 деÑ\82аÑ\99има о коÑ\80иÑ\81никÑ\83 коÑ\98и Ñ\98е Ñ\83Ñ\80едио овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 пÑ\80е бÑ\80иÑ\81аÑ\9aа.\nТекÑ\81Ñ\82 обрисаних ревизија је доступан само администраторима.",
- "undelete-revision": "Ð\9eбÑ\80иÑ\81ана измена Ñ\81Ñ\82Ñ\80аниÑ\86е $1 (дана $4; $5) од Ñ\81Ñ\82Ñ\80ане {{GENDER:$3|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86е|коÑ\80иÑ\81ника}} $3:",
- "undeleterevision-missing": "Ð\9dеважеÑ\9bа или недоÑ\81Ñ\82аÑ\98Ñ\83Ñ\9bа Ñ\80евизиÑ\98а.\nÐ\9cожда Ñ\81Ñ\82е Ñ\83нели погÑ\80еÑ\88нÑ\83 везÑ\83 или је ревизија враћена или уклоњена из архиве.",
+ "undeleterevdel": "Ð\92Ñ\80аÑ\9bаÑ\9aе неÑ\9bе биÑ\82и извÑ\80Ñ\88ено ако Ñ\98е Ñ\80езÑ\83лÑ\82аÑ\82 Ñ\82ога делимиÑ\87но бÑ\80иÑ\81аÑ\9aе поÑ\81ледÑ\9aе Ñ\80евизиÑ\98е.\nУ Ñ\82аквим Ñ\81лÑ\83Ñ\87аÑ\98евима моÑ\80аÑ\82е иÑ\81кÑ\99Ñ\83Ñ\87иÑ\82и или оÑ\82кÑ\80иÑ\82и наÑ\98новиÑ\98е избрисане ревизије.",
+ "undeletehistorynoadmin": "Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избÑ\80иÑ\81ана.\nРазлог за бÑ\80иÑ\81аÑ\9aе Ñ\81е налази иÑ\81под, заÑ\98едно Ñ\81а деÑ\82аÑ\99има о коÑ\80иÑ\81никÑ\83 коÑ\98и Ñ\98е Ñ\83Ñ\80едио овÑ\83 Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 пÑ\80е бÑ\80иÑ\81аÑ\9aа.\nТекÑ\81Ñ\82 избрисаних ревизија је доступан само администраторима.",
+ "undelete-revision": "Ð\98збÑ\80иÑ\81ана Ñ\80евизиÑ\98а Ñ\81Ñ\82Ñ\80аниÑ\86е $1 (дана $4; $5) од Ñ\81Ñ\82Ñ\80ане {{GENDER:$3|коÑ\80иÑ\81ника|коÑ\80иÑ\81ниÑ\86е}} $3:",
+ "undeleterevision-missing": "Ð\9dеважеÑ\9bа или недоÑ\81Ñ\82аÑ\98Ñ\83Ñ\9bа Ñ\80евизиÑ\98а.\nÐ\9cожда Ñ\81Ñ\82е Ñ\83нели лоÑ\88 линк или је ревизија враћена или уклоњена из архиве.",
"undeleterevision-duplicate-revid": "Не могу вратити {{PLURAL:$1|ревизију|$1 ревизије|$1 ревизија}} јер се {{PLURAL:$1|њен|њихов}} <code>rev_id</code> већ користи.",
"undelete-nodiff": "Претходне измене нису пронађене.",
"undeletebtn": "Врати",
"undeletecomment": "Разлог:",
"cannotundelete": "Враћање једне или свих није успело:\n$1",
"undeletedpage": "<strong>Страница $1 је враћена</strong>\n\nПогледајте [[Special:Log/delete|евиденцију брисања]] за записе о недавним брисањима и враћањима.",
- "undelete-header": "Ð\9fогледаÑ\98Ñ\82е [[Special:Log/delete|евиденÑ\86иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа]] за недавно обрисане странице.",
- "undelete-search-title": "Ð\9fÑ\80еÑ\82Ñ\80ага обрисаних страница",
- "undelete-search-box": "Ð\9fÑ\80еÑ\82Ñ\80ажи обÑ\80иÑ\81ане Ñ\81Ñ\82Ñ\80аниÑ\86е",
+ "undelete-header": "Ð\9fогледаÑ\98Ñ\82е [[Special:Log/delete|евиденÑ\86иÑ\98Ñ\83 бÑ\80иÑ\81аÑ\9aа]] за недавно избрисане странице.",
+ "undelete-search-title": "Ð\9fÑ\80еÑ\82Ñ\80ага избрисаних страница",
+ "undelete-search-box": "Ð\9fÑ\80еÑ\82Ñ\80ага избÑ\80иÑ\81аниÑ\85 Ñ\81Ñ\82Ñ\80аниÑ\86а",
"undelete-search-prefix": "Прикажи странице које почињу са:",
"undelete-search-full": "Прикажи наслове који садрже:",
"undelete-search-submit": "Претражи",
"undelete-bad-store-key": "Не могу да вратим измену датотеке од $1: датотека је недостајала пре брисања.",
"undelete-cleanup-error": "Грешка при брисању некоришћене архиве „$1“.",
"undelete-missing-filearchive": "Не могу да вратим архиву с ИБ $1 јер се она не налази у бази података.\nМожда је већ била враћена.",
- "undelete-error": "Ð\94оÑ\88ло Ñ\98е до гÑ\80еÑ\88ке пÑ\80и вÑ\80аÑ\9bаÑ\9aÑ\83 обрисане странице",
+ "undelete-error": "Ð\94оÑ\88ло Ñ\98е до гÑ\80еÑ\88ке пÑ\80и вÑ\80аÑ\9bаÑ\9aÑ\83 избрисане странице",
"undelete-error-short": "Грешка при враћању датотеке: $1",
"undelete-error-long": "Дошло је до грешке при враћању датотеке:\n\n$1",
- "undelete-show-file-confirm": "Ð\94а ли Ñ\81Ñ\82е Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да погледаÑ\82е обÑ\80иÑ\81анÑ\83 измену датотеке „<nowiki>$1</nowiki>“ од $2 у $3?",
+ "undelete-show-file-confirm": "Ð\88еÑ\81Ñ\82е ли Ñ\81игÑ\83Ñ\80ни да желиÑ\82е да погледаÑ\82е избÑ\80иÑ\81анÑ\83 Ñ\80евизиÑ\98у датотеке „<nowiki>$1</nowiki>“ од $2 у $3?",
"undelete-show-file-submit": "Да",
"namespace": "Именски простор:",
"invert": "Обрни избор",
"tooltip-invert": "Означите ову кутијуцу да бисте сакрили промене на страницана у изабраном именском простору (и повезаним именским просторима, ако је означено)",
- "tooltip-whatlinkshere-invert": "Ð\9eзнаÑ\87иÑ\82е овÑ\83 кÑ\83Ñ\82иÑ\98иÑ\86Ñ\83 за Ñ\81акÑ\80иваÑ\9aе веза са страница у изабраном именском простору.",
+ "tooltip-whatlinkshere-invert": "Ð\9eзнаÑ\87иÑ\82е овÑ\83 кÑ\83Ñ\82иÑ\98иÑ\86Ñ\83 за Ñ\81акÑ\80иваÑ\9aе линкова са страница у изабраном именском простору.",
"namespace_association": "Повезани именски простор",
"tooltip-namespace_association": "Означите ову кутијицу да бисте укључили и разговор или именски простор теме која је повезана са изабраним именским простором",
"blanknamespace": "(главни)",
"sp-contributions-newbies-sub": "За нове кориснике",
"sp-contributions-newbies-title": "Доприноси нових корисника",
"sp-contributions-blocklog": "евиденција блокирања",
- "sp-contributions-suppresslog": "обрисани доприноси {{GENDER:$1|корисника|кориснице}}",
- "sp-contributions-deleted": "обрисани доприноси {{GENDER:$1|корисника|кориснице}}",
+ "sp-contributions-suppresslog": "избрисани доприноси {{GENDER:$1|корисника|кориснице}}",
+ "sp-contributions-deleted": "избрисани доприноси {{GENDER:$1|корисника|кориснице}}",
"sp-contributions-uploads": "отпремања",
"sp-contributions-logs": "евиденције",
"sp-contributions-talk": "разговор",
"sp-contributions-hideminor": "Сакриј мање измене",
"sp-contributions-submit": "Претражи",
"whatlinkshere": "Шта води овде",
- "whatlinkshere-title": "Странице које су повезане са „$1”",
+ "whatlinkshere-title": "Странице које воде на страницу „$1”",
"whatlinkshere-page": "Страница:",
- "linkshere": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е имаÑ\98Ñ\83 везÑ\83 до <strong>$2</strong>:",
+ "linkshere": "СледеÑ\9bе Ñ\81Ñ\82Ñ\80аниÑ\86е воде на Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 <strong>$2</strong>:",
"nolinkshere": "Ниједна страница није повезана са: <strong>$2</strong>.",
- "nolinkshere-ns": "Ð\9dиÑ\98една Ñ\81Ñ\82Ñ\80аниÑ\86а не води до '''$2''' у изабраном именском простору.",
+ "nolinkshere-ns": "Ð\9dиÑ\98една Ñ\81Ñ\82Ñ\80аниÑ\86а не води на Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 <strong>$2</strong> у изабраном именском простору.",
"isredirect": "преусмерење",
"istemplate": "укључивање",
- "isimage": "веза до датотеке",
+ "isimage": "линк до датотеке",
"whatlinkshere-prev": "{{PLURAL:$1|претходни|претходна $1|претходних $1}}",
"whatlinkshere-next": "{{PLURAL:$1|следећи|следећа $1|следећих $1}}",
- "whatlinkshere-links": "â\86\90 везе",
+ "whatlinkshere-links": "â\86\90 линкови",
"whatlinkshere-hideredirs": "$1 преусмерења",
"whatlinkshere-hidetrans": "$1 укључивања",
- "whatlinkshere-hidelinks": "$1 везе",
- "whatlinkshere-hideimages": "$1 везе до датотеке",
+ "whatlinkshere-hidelinks": "$1 линкове",
+ "whatlinkshere-hideimages": "$1 линкова до датотеке",
"whatlinkshere-filters": "Филтери",
"whatlinkshere-submit": "Иди",
"autoblockid": "Аутоматско блокирање #$1",
"ipaddressorusername": "IP адреса или корисничко име:",
"ipbexpiry": "Истиче:",
"ipbreason": "Разлог:",
- "ipbreason-dropdown": "*Ð\9dаÑ\98Ñ\87еÑ\88Ñ\9bи Ñ\80азлози за блокиÑ\80аÑ\9aе\n** УноÑ\88еÑ\9aе лажниÑ\85 инÑ\84оÑ\80маÑ\86иÑ\98а\n** УклаÑ\9aаÑ\9aе Ñ\81адÑ\80жаÑ\98а Ñ\81а Ñ\81Ñ\82Ñ\80аниÑ\86а\n** Ð\9fоÑ\81Ñ\82авÑ\99аÑ\9aе веза до Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81аÑ\98Ñ\82ова\n** УноÑ\88еÑ\9aе беÑ\81миÑ\81лиÑ\86а у странице\n** Непристојно понашање\n** Употреба више налога\n** Неприхватљиво корисничко име",
+ "ipbreason-dropdown": "*Ð\9dаÑ\98Ñ\87еÑ\88Ñ\9bи Ñ\80азлози за блокиÑ\80аÑ\9aе\n** УмеÑ\82аÑ\9aе лажниÑ\85 инÑ\84оÑ\80маÑ\86иÑ\98а\n** УклаÑ\9aаÑ\9aе Ñ\81адÑ\80жаÑ\98а Ñ\81а Ñ\81Ñ\82Ñ\80аниÑ\86а\n** Ð\94одаваÑ\9aе непожеÑ\99ниÑ\85 линкова до Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81аÑ\98Ñ\82ова\n** УноÑ\88еÑ\9aе беÑ\81миÑ\81лиÑ\86а/гÑ\80аÑ\84иÑ\82а у странице\n** Непристојно понашање\n** Употреба више налога\n** Неприхватљиво корисничко име",
"ipb-hardblock": "Спречи пријављене кориснике да уређују с ове IP адресе",
"ipbcreateaccount": "Онемогући отварање налога",
"ipbemailban": "Спречи корисника да шаље имејлове",
"lockedbyandtime": "(од $1 дана $2 у $3)",
"move-page": "Премештање „$1”",
"move-page-legend": "Премештање странице",
- "movepagetext": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9cожеÑ\82е ажÑ\83Ñ\80иÑ\80аÑ\82и пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа коÑ\98а воде до извоÑ\80ног наÑ\81лова;\nпогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да везе и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
- "movepagetext-noredirectfixer": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9fогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да везе и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
+ "movepagetext": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9cожеÑ\82е ажÑ\83Ñ\80иÑ\80аÑ\82и пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа коÑ\98а воде до извоÑ\80ног наÑ\81лова;\nпогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да линкови и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
+ "movepagetext-noredirectfixer": "Ð\94оÑ\9aи обÑ\80азаÑ\86 Ñ\9bе пÑ\80еименоваÑ\82и Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83, пÑ\80емеÑ\88Ñ\82аÑ\98Ñ\83Ñ\9bи Ñ\86елÑ\83 иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 на ново име.\nСÑ\82аÑ\80и наÑ\81лов поÑ\81Ñ\82аÑ\9bе пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aе на нови.\nÐ\9fогледаÑ\98Ñ\82е [[Special:DoubleRedirects|двоÑ\81Ñ\82Ñ\80Ñ\83ка]] или [[Special:BrokenRedirects|покваÑ\80ена]] пÑ\80еÑ\83Ñ\81меÑ\80еÑ\9aа.\nÐ\9dа вама Ñ\98е одговоÑ\80ноÑ\81Ñ\82 да линкови и даље иду тамо где треба.\n\nСтраница <strong>неће</strong> бити премештена ако већ постоји страница с тим именом (осим ако је празна, садржи преусмерење или нема историју измена).\nТо значи да можете вратити страницу на претходно име ако погрешите, али не можете ''преписати'' постојећу.\n\n<strong>Напомена:</strong>\nОво може представљати драстичну и неочекивану измену за популарну страницу;\nдобро размислите о последицама пре него што наставите.",
"movepagetalktext": "Ако сте означили овај квадратић, одговарајућа страница за разговор биће аутоматски премештена на нови наслов, осим ако већ постоји страница за разговор са истим насловом.\n\nУ том случају, мораћете ручно да је преместите или спојите, ако има потребе за тим.",
"moveuserpage-warning": "'''Упозорење:''' на путу сте да преместите корисничку страницу. Имајте у виду да ће само страница бити премештена, а сам корисник ''неће'' бити преименован.",
"movecategorypage-warning": "<strong>Упозорење:</strong> премештате страницу категорије. Имајте на уму да ће само страница бити премештена и да све странице у старој категорији <em>неће</em> бити рекатегорисане у нову категорију.",
"movepage-moved": "'''„$1“ је премештена на „$2“'''",
"movepage-moved-redirect": "Преусмерење је направљено.",
"movepage-moved-noredirect": "Стварање преусмерења је онемогућено.",
- "articleexists": "Страница с тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.",
+ "articleexists": "Страница са тим именом већ постоји или име које сте одабрали није важеће.\nОдаберите друго.",
"cantmove-titleprotected": "Не можете да преместите страницу на то место јер је жељени наслов заштићен од стварања",
"movetalk": "Премести и страницу за разговор",
"move-subpages": "Премести и подстранице (до $1)",
"movenosubpage": "Ова страница нема подстрана.",
"movereason": "Разлог:",
"revertmove": "врати",
- "delete_and_move_text": "Ð\9eдÑ\80едиÑ\88на Ñ\81Ñ\82Ñ\80аниÑ\86а â\80\9e[[:$1]]â\80\9c веÑ\9b поÑ\81Ñ\82оÑ\98и. \nÐ\96елиÑ\82е ли да Ñ\98е обришете да бисте ослободили место за премештање?",
- "delete_and_move_confirm": "Ð\94а, обриши страницу",
- "delete_and_move_reason": "Ð\9eбрисано да се ослободи место за премештање из „[[$1]]“",
+ "delete_and_move_text": "Ð\9eдÑ\80едиÑ\88на Ñ\81Ñ\82Ñ\80аниÑ\86а â\80\9e[[:$1]]â\80\9c веÑ\9b поÑ\81Ñ\82оÑ\98и. \nÐ\96елиÑ\82е ли да Ñ\98е избришете да бисте ослободили место за премештање?",
+ "delete_and_move_confirm": "Ð\94а, избриши страницу",
+ "delete_and_move_reason": "Ð\98збрисано да се ослободи место за премештање из „[[$1]]“",
"selfmove": "Наслов је истоветан;\nне можете преместити страницу преко саме себе.",
"immobile-source-namespace": "Не могу преместити странице у именски простор „$1“.",
"immobile-target-namespace": "Не могу преместити странице у именски простор „$1“.",
- "immobile-target-namespace-iw": "Ð\9cеÑ\92Ñ\83вики веза ниÑ\98е валидно одредиште за премештање странице.",
+ "immobile-target-namespace-iw": "Ð\9cеÑ\92Ñ\83вики линк ниÑ\98е важеÑ\9bе одредиште за премештање странице.",
"immobile-source-page": "Ова страница се не може преместити.",
"immobile-target-page": "Не могу да преместим на жељени наслов.",
"bad-target-model": "Жељено одредиште користи другачији модел садржаја. Не могу да претворим из $1 у $2.",
"move-over-sharedrepo": "[[:$1]] се налази на дељеном складишту. Ако преместите датотеку на овај наслов, то ће заменити дељену датотеку.",
"file-exists-sharedrepo": "Наведени назив датотеке се већ користи у дељеном складишту.\nИзаберите други назив.",
"export": "Извоз страница",
- "exporttext": "Ð\9cожеÑ\82е да извезеÑ\82е Ñ\82екÑ\81Ñ\82 и иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 измена одÑ\80еÑ\92ене Ñ\81Ñ\82Ñ\80аниÑ\86е или Ñ\81кÑ\83па Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83клÑ\99ениÑ\85 Ñ\83 XML Ñ\84оÑ\80маÑ\82Ñ\83.\nÐ\9eво онда може да бÑ\83де Ñ\83везено Ñ\83 дÑ\80Ñ\83ги вики коÑ\98и коÑ\80иÑ\81Ñ\82и Ð\9cедиÑ\98авики Ñ\81оÑ\84Ñ\82веÑ\80 пÑ\80еко [[Special:Import|Ñ\81Ñ\82Ñ\80аниÑ\86е за Ñ\83воз]].\n\nÐ\94а биÑ\81Ñ\82е извезли Ñ\81Ñ\82Ñ\80аниÑ\86е, Ñ\83неÑ\81иÑ\82е називе Ñ\83 оквиÑ\80Ñ\83 иÑ\81под, Ñ\81 Ñ\98едним наÑ\81ловом по Ñ\80едÑ\83, и изабеÑ\80иÑ\82е да ли желиÑ\82е акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 и Ñ\81ве оÑ\81Ñ\82але, или Ñ\81амо акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 Ñ\81 подаÑ\86има о поÑ\81ледÑ\9aоÑ\98 измени.\n\nУ дÑ\80Ñ\83гом Ñ\81лÑ\83Ñ\87аÑ\98Ñ\83, можеÑ\82е коÑ\80иÑ\81Ñ\82иÑ\82и и везÑ\83, на пример [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] за страницу [[{{MediaWiki:Mainpage}}]].",
+ "exporttext": "Ð\9cожеÑ\82е да извезеÑ\82е Ñ\82екÑ\81Ñ\82 и иÑ\81Ñ\82оÑ\80иÑ\98Ñ\83 измена одÑ\80еÑ\92ене Ñ\81Ñ\82Ñ\80аниÑ\86е или Ñ\81кÑ\83па Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\83клÑ\99ениÑ\85 Ñ\83 XML Ñ\84оÑ\80маÑ\82Ñ\83.\nÐ\9eво онда може да бÑ\83де Ñ\83везено Ñ\83 дÑ\80Ñ\83ги вики коÑ\98и коÑ\80иÑ\81Ñ\82и Ð\9cедиÑ\98авики Ñ\81оÑ\84Ñ\82веÑ\80 пÑ\80еко [[Special:Import|Ñ\81Ñ\82Ñ\80аниÑ\86е за Ñ\83воз]].\n\nÐ\94а биÑ\81Ñ\82е извезли Ñ\81Ñ\82Ñ\80аниÑ\86е, Ñ\83неÑ\81иÑ\82е називе Ñ\83 оквиÑ\80Ñ\83 иÑ\81под, Ñ\81 Ñ\98едним наÑ\81ловом по Ñ\80едÑ\83, и изабеÑ\80иÑ\82е да ли желиÑ\82е акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 и Ñ\81ве оÑ\81Ñ\82але, или Ñ\81амо акÑ\82Ñ\83елнÑ\83 Ñ\80евизиÑ\98Ñ\83 Ñ\81 подаÑ\86има о поÑ\81ледÑ\9aоÑ\98 измени.\n\nУ дÑ\80Ñ\83гом Ñ\81лÑ\83Ñ\87аÑ\98Ñ\83, можеÑ\82е коÑ\80иÑ\81Ñ\82иÑ\82и и линк, на пример [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]] за страницу [[{{MediaWiki:Mainpage}}]].",
"exportall": "Извези све странице",
"exportcuronly": "Укључи само актуелну ревизију, не целу историју",
"exportnohistory": "----\n'''Напомена:''' извоз пуне историје страница преко овог обрасца је онемогућено из техничких разлога.",
"djvu_no_xml": "Не могу да преузмем XML за DjVu датотеку.",
"thumbnail-temp-create": "Не могу да направим привремену датотеку минијатуре",
"thumbnail-dest-create": "Не могу да сачувам минијатуру у одредишту",
- "thumbnail_invalid_params": "Ð\9dеиÑ\81пÑ\80авни паÑ\80амеÑ\82Ñ\80и за миниÑ\98аÑ\82Ñ\83Ñ\80Ñ\83",
+ "thumbnail_invalid_params": "Ð\9dеважеÑ\9bи паÑ\80амеÑ\82Ñ\80и Ñ\81лиÑ\87иÑ\86е",
"thumbnail_toobigimagearea": "Датотека са величинама већим од $1",
"thumbnail_dest_directory": "Не могу да направим одредишну фасциклу",
"thumbnail_image-type": "Тип слике није подржан",
"import-upload-filename": "Назив датотеке:",
"import-upload-username-prefix": "Међувики префикс:",
"import-comment": "Коментар:",
- "importtext": "Извезите датотеку с изворног викија користећи [[Special:Export|извоз]].\nСачувајте је на рачунар и пошаљите овде.",
+ "importtext": "Извезите датотеку сa изворног викија користећи [[Special:Export|алат за извоз]].\nСачувајте је на рачунар и оптремите овде.",
"importstart": "Увозим странице…",
"import-revision-count": "$1 {{PLURAL:$1|ревизија|ревизије|ревизија}}",
"importnopages": "Нема страница за увоз.",
"importunknownsource": "Непознат изворни тип увоза",
"importnoprefix": "Није наведен међувики префикс",
"importcantopen": "Не могу да отворим датотеку за увоз.",
- "importbadinterwiki": "Ð\9dеиÑ\81пÑ\80авна меÑ\92Ñ\83вики веза",
+ "importbadinterwiki": "Ð\9bоÑ\88 меÑ\92Ñ\83вики линк",
"importsuccess": "Увожење је завршено!",
"importnosources": "Није одређен ниједан извор за увоз, тако да је отпремање историје онемогућено.",
"importnofile": "Увозна датотека није послата.",
"import-error-create": "Страница „$1“ није увезена јер вам није дозвољено да је направите.",
"import-error-interwiki": "Не могу да увезем страницу „$1“ јер је њен назив резервисан за спољно повезивање (међувики).",
"import-error-special": "Не могу да увезем страницу „$1“ јер она припада посебном именском простору које не прихвата странице.",
- "import-error-invalid": "Ð\9dе могÑ\83 да Ñ\83везем Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 â\80\9e$1â\80\9c Ñ\98еÑ\80 Ñ\98е Ñ\9aен назив неиÑ\81пÑ\80аван.",
+ "import-error-invalid": "СÑ\82Ñ\80аниÑ\86а â\80\9e$1â\80\9c ниÑ\98е Ñ\83везена Ñ\98еÑ\80 Ñ\98е име под коÑ\98им Ñ\81е Ñ\82Ñ\80еба Ñ\83воÑ\81Ñ\82и неважеÑ\9bе на овом викиÑ\98Ñ\83.",
"import-error-unserialize": "Не могу да десеријализујем ревизију $2 странице $1. Записано је да ревизија користи $3 модел садржаја у $4 формату.",
"import-options-wrong": "{{PLURAL:$2|Погрешна опција|Погрешне опције}}: <nowiki>$1</nowiki>",
- "import-rootpage-invalid": "Ð\9dаведена оÑ\81новна Ñ\81Ñ\82Ñ\80аниÑ\86а има неиÑ\81пÑ\80аван наслов.",
+ "import-rootpage-invalid": "Ð\9dаведена оÑ\81новна Ñ\81Ñ\82Ñ\80аниÑ\86а има неважеÑ\9bи наслов.",
"import-rootpage-nosubpage": "Именски простор „$1“ основне странице не дозвољава подстранице.",
"importlogpage": "Евиденција увоза",
"importlogpagetext": "Административни увози страница с историјама измена с других викија.",
"tooltip-ca-history": "Претходне ревизије ове странице",
"tooltip-ca-protect": "Заштитите ову страницу",
"tooltip-ca-unprotect": "Промени заштиту ове странице",
- "tooltip-ca-delete": "Ð\9eбришите ову страницу",
- "tooltip-ca-undelete": "Ð\92Ñ\80аÑ\82и измене напÑ\80авÑ\99ене на овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и пÑ\80е него Ñ\88Ñ\82о бÑ\83де обÑ\80иÑ\81ана",
+ "tooltip-ca-delete": "Ð\98збришите ову страницу",
+ "tooltip-ca-undelete": "Ð\92Ñ\80аÑ\82и измене коÑ\98е Ñ\81Ñ\83 наÑ\87иÑ\9aене на овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и пÑ\80е бÑ\80иÑ\81аÑ\9aа Ñ\81Ñ\82Ñ\80аниÑ\86е",
"tooltip-ca-move": "Премести ову страницу",
"tooltip-ca-watch": "Додајте ову страницу на свој списак надгледања",
"tooltip-ca-unwatch": "Уклоните ову страницу са списка надгледања",
"tooltip-t-upload": "Отпремите датотеке",
"tooltip-t-specialpages": "Списак свих посебних страница",
"tooltip-t-print": "Верзија ове странице за штампање",
- "tooltip-t-permalink": "ТÑ\80аÑ\98на веза ка овој ревизији странице",
+ "tooltip-t-permalink": "ТÑ\80аÑ\98ни линк ка овој ревизији странице",
"tooltip-ca-nstab-main": "Погледајте страницу са садржајем",
"tooltip-ca-nstab-user": "Погледајте корисничку страницу",
"tooltip-ca-nstab-media": "Погледајте медијску страницу",
"tooltip-minoredit": "Означите ову измену као мању",
"tooltip-save": "Сачувајте своје промене",
"tooltip-publish": "Објавите своје измене",
- "tooltip-preview": "Ð\9fÑ\80егледаÑ\98Ñ\82е Ñ\81воÑ\98е измене. Користите ово дугме пре чувања.",
+ "tooltip-preview": "Ð\9fÑ\80егледаÑ\98Ñ\82е Ñ\81воÑ\98е пÑ\80омене. Користите ово дугме пре чувања.",
"tooltip-diff": "Погледајте које промене сте направили на тексту",
"tooltip-compareselectedversions": "Погледаjте разлике између две изабране ревизије ове странице",
"tooltip-watch": "Додајте ову страницу на свој списак надгледања",
"tooltip-watchlistedit-normal-submit": "Уклоните наслове",
"tooltip-watchlistedit-raw-submit": "Ажурирај списак",
- "tooltip-recreate": "Ð\9fоново напÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 иако Ñ\98е обрисана",
+ "tooltip-recreate": "Ð\9fоново напÑ\80авиÑ\82е Ñ\81Ñ\82Ñ\80аниÑ\86Ñ\83 иако Ñ\98е веÑ\9b избрисана",
"tooltip-upload": "Започните отпремање",
"tooltip-rollback": "„Врати“ враћа измене последњег доприносиоца ове странице једним кликом",
"tooltip-undo": "„Поништи” враћа ову измену и отвара образац за уређивање у претпрегледном моду. Дозвољава додавање разлога у резимеу.",
"creditspage": "Аутори странице",
"nocredits": "Не постоје подаци о аутору ове странице.",
"spamprotectiontitle": "Филтер за заштиту од непожељних порука",
- "spamprotectiontext": "ФилÑ\82еÑ\80а пÑ\80оÑ\82ив нежеÑ\99ениÑ\85 поÑ\80Ñ\83ка Ñ\98е блокиÑ\80ао Ñ\87Ñ\83ваÑ\9aе ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eво Ñ\98е веÑ\80оваÑ\82но изазвано везом до спољашњег сајта који се налази на црном списку.",
+ "spamprotectiontext": "ФилÑ\82еÑ\80а пÑ\80оÑ\82ив нежеÑ\99ениÑ\85 поÑ\80Ñ\83ка Ñ\98е блокиÑ\80ао Ñ\87Ñ\83ваÑ\9aе ове Ñ\81Ñ\82Ñ\80аниÑ\86е.\nÐ\9eво Ñ\98е веÑ\80оваÑ\82но изазвано линком до спољашњег сајта који се налази на црном списку.",
"spamprotectionmatch": "Следећи текст је активирао наш филтер за нежељене поруке: $1",
"spambot_username": "Чишћење непожељних порука у Медијавикији",
- "spam_reverting": "Ð\92Ñ\80аÑ\9bам на поÑ\81ледÑ\9aÑ\83 Ñ\80евизиÑ\98Ñ\83 коÑ\98а не Ñ\81адÑ\80жи везе до $1",
- "spam_blanking": "Све измене садрже везе до $1. Чистим",
- "spam_deleting": "Све Ñ\80евизиÑ\98е Ñ\81адÑ\80же везе до $1. Бришем",
+ "spam_reverting": "Ð\92Ñ\80аÑ\9bам на поÑ\81ледÑ\9aÑ\83 Ñ\80евизиÑ\98Ñ\83 коÑ\98а не Ñ\81адÑ\80жи линкове до $1",
+ "spam_blanking": "Све ревизије садрже линкове до $1. Празним",
+ "spam_deleting": "Све Ñ\80евизиÑ\98е Ñ\81адÑ\80же линкове до $1. Бришем",
"simpleantispam-label": "Провера против нежељеног садржаја. \n<strong>Не</strong> попуњавајте ово!",
"pageinfo-title": "Информације за „$1“",
"pageinfo-not-current": "Нажалост, немогуће је навести ове инфомације за старије ревизије.",
"markaspatrolledtext": "Означи страницу као патролирану",
"markaspatrolledtext-file": "Означи ову верзију датотеке као патролирану",
"markedaspatrolled": "Означено као патролирано",
- "markedaspatrolledtext": "Изабрана ревизија странице [[:$1]] је означена као патролирана.",
+ "markedaspatrolledtext": "Изабрана ревизија странице [[:$1]] означена је као патролирана.",
"rcpatroldisabled": "Патролирање скорашњих измена је онемогућено",
"rcpatroldisabledtext": "Могућност патролирања скорашњих измена је актуелно онемогућена.",
"markedaspatrollederror": "Не могу да означим као патролирано.",
"patrol-log-header": "Ово је евиденција патролираних ревизија.",
"confirm-markpatrolled-button": "У реду",
"confirm-markpatrolled-top": "Означити ревизију $3 странице $2 као патролирану?",
- "deletedrevision": "Ð\9eбрисана стара ревизија $1.",
+ "deletedrevision": "Ð\98збрисана стара ревизија $1.",
"filedeleteerror-short": "Грешка при брисању датотеке: $1",
"filedeleteerror-long": "Дошло је до грешака при брисању датотеке:\n\n$1",
- "filedelete-missing": "Ð\94аÑ\82оÑ\82ека â\80\9e$1â\80\9c Ñ\81е не може обÑ\80иÑ\81аÑ\82и јер не постоји.",
+ "filedelete-missing": "Ð\9dе могÑ\83 да избÑ\80иÑ\88ем даÑ\82оÑ\82екÑ\83 â\80\9e$1â\80\9c јер не постоји.",
"filedelete-old-unregistered": "Наведена ревизија датотеке „$1“ не постоји у бази података.",
"filedelete-current-unregistered": "Наведена датотека „$1“ не постоји у бази података.",
"filedelete-archive-read-only": "Сервер не може да пише по складишној фасцикли ($1).",
"file-nohires": "Већа резолуција није доступна.",
"svg-long-desc": "SVG датотека, номинално $1 × $2 пиксела, величина: $3",
"svg-long-desc-animated": "Анимирана SVG датотека, номинално: $1 × $2 пиксела, величина: $3",
- "svg-long-error": "Ð\9dеиÑ\81пÑ\80авна SVG датотека: $1",
+ "svg-long-error": "Ð\9dеважеÑ\9bа SVG датотека: $1",
"show-big-image": "Првобитна датотека",
"show-big-image-preview": "Величина овог приказа: $1.",
"show-big-image-preview-differ": "Величина $3 прегледа за ову $2 датотеку је $1.",
"newimages-label": "Назив датотеке (или њен део):",
"newimages-user": "IP адреса или корисничко име",
"newimages-newbies": "Прикажи само доприносе нових налога",
- "newimages-showbots": "Ð\9fÑ\80икажи даÑ\82оÑ\82еке коÑ\98е Ñ\81Ñ\83 поÑ\81лали боÑ\82ови",
+ "newimages-showbots": "Ð\9fÑ\80икажи оÑ\82пÑ\80емаÑ\9aа боÑ\82ова",
"newimages-hidepatrolled": "Сакриј патролирана отпремања",
"newimages-mediatype": "Тип датотеке:",
"noimages": "Нема ништа.",
"saturday-at": "у суботу у $1",
"sunday-at": "у недељу у $1",
"yesterday-at": "Јуче у $1",
- "bad_image_list": "ФоÑ\80маÑ\82 Ñ\98е Ñ\81ледеÑ\9bи:\n\nРазмаÑ\82Ñ\80аÑ\98Ñ\83 Ñ\81е Ñ\81амо набÑ\80аÑ\98аÑ\9aа (Ñ\80едови коÑ\98и поÑ\87иÑ\9aÑ\83 Ñ\81а звездиÑ\86ом).\nÐ\9fÑ\80ва веза Ñ\83 Ñ\80едÑ\83 моÑ\80а да бÑ\83де веза до неиÑ\81пÑ\80авне даÑ\82оÑ\82еке.\nСве даÑ\99Ñ\9aе везе у истом реду сматрају се изузецима.",
+ "bad_image_list": "ФоÑ\80маÑ\82 Ñ\98е Ñ\81ледеÑ\9bи:\n\nРазмаÑ\82Ñ\80аÑ\98Ñ\83 Ñ\81е Ñ\81амо набÑ\80аÑ\98аÑ\9aа (Ñ\80едови коÑ\98и поÑ\87иÑ\9aÑ\83 Ñ\81а звездиÑ\86ом).\nÐ\9fÑ\80ви линк Ñ\83 Ñ\80едÑ\83 моÑ\80а да бÑ\83де линк до неиÑ\81пÑ\80авне даÑ\82оÑ\82еке.\nСви даÑ\99Ñ\9aи линкови у истом реду сматрају се изузецима.",
"variantname-zh-hans": "hans",
"variantname-zh-hant": "hant",
"variantname-zh-cn": "cn",
"exif-originaldocumentid": "Јединствени ID изворног документа",
"exif-licenseurl": "Адреса лиценце за ауторска права",
"exif-morepermissionsurl": "Резервни подаци о лиценцирању",
- "exif-attributionurl": "Ð\9fÑ\80и поновном коÑ\80иÑ\88Ñ\9bеÑ\9aÑ\83 овог Ñ\80ада, коÑ\80иÑ\81Ñ\82иÑ\82е везÑ\83 до",
+ "exif-attributionurl": "Ð\9fÑ\80и поновном коÑ\80иÑ\88Ñ\9bеÑ\9aÑ\83 овог Ñ\80ада, коÑ\80иÑ\81Ñ\82иÑ\82е линк до",
"exif-preferredattributionname": "При поновном коришћењу овог рада, поставите заслуге",
"exif-pngfilecomment": "Коментар на датотеку PNG",
"exif-disclaimer": "Одрицање одговорности",
"exif-urgency-other": "Прилагођени приоритет ($1)",
"namespacesall": "сви",
"monthsall": "све",
- "confirmemail": "Потврда имејл адресе",
- "confirmemail_noemail": "Нисте унели валидну имејл адресу у [[Special:Preferences|подешавањима]].",
- "confirmemail_text": "{{SITENAME}} заÑ\85Ñ\82ева да поÑ\82вÑ\80диÑ\82е имеÑ\98л адÑ\80еÑ\81Ñ\83 пÑ\80е него Ñ\88Ñ\82о поÑ\87неÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82и имеÑ\98ла.\nÐ\9aликниÑ\82е на дÑ\83гме иÑ\81под за Ñ\81лаÑ\9aе поÑ\80Ñ\83ке на ваÑ\88Ñ\83 адÑ\80еÑ\81Ñ\83.\nУ поÑ\80Ñ\83Ñ\86и Ñ\9bе Ñ\81е налазиÑ\82и веза Ñ\81 потврдним кодом;\nунесите је у прегледач да бисте потврдили да је ваша имејл адреса важећа.",
- "confirmemail_pending": "Ð\9fоÑ\82вÑ\80дни код вам Ñ\98е веÑ\9b поÑ\81лаÑ\82. Ð\90ко Ñ\81Ñ\82е недавно оÑ\82воÑ\80или налог, онда веÑ\80оваÑ\82но Ñ\82Ñ\80еба да Ñ\81аÑ\87екаÑ\82е неколико минÑ\83Ñ\82а да пÑ\80иÑ\81Ñ\82игне, пре него што поново затражите нови код.",
- "confirmemail_send": "Ð\9fоÑ\88аÑ\99и поÑ\82вÑ\80дни код",
+ "confirmemail": "Потврда имејл-адресе",
+ "confirmemail_noemail": "Нисте поставили важећу имејл-адресу у [[Special:Preferences|корисничким подешавањима]].",
+ "confirmemail_text": "{{SITENAME}} заÑ\85Ñ\82ева да поÑ\82вÑ\80диÑ\82е имеÑ\98л адÑ\80еÑ\81Ñ\83 пÑ\80е него Ñ\88Ñ\82о поÑ\87неÑ\82е да коÑ\80иÑ\81Ñ\82иÑ\82е могÑ\83Ñ\9bноÑ\81Ñ\82и имеÑ\98ла.\nÐ\9aликниÑ\82е на дÑ\83гме иÑ\81под за Ñ\81лаÑ\9aе поÑ\80Ñ\83ке на ваÑ\88Ñ\83 адÑ\80еÑ\81Ñ\83.\nУ поÑ\80Ñ\83Ñ\86и Ñ\9bе Ñ\81е налазиÑ\82и линк Ñ\81а потврдним кодом;\nунесите је у прегледач да бисте потврдили да је ваша имејл адреса важећа.",
+ "confirmemail_pending": "Ð\9aод за поÑ\82вÑ\80дÑ\83 вам Ñ\98е веÑ\9b поÑ\81лаÑ\82 имеÑ\98лом.\nÐ\90ко Ñ\81Ñ\82е недавно оÑ\82воÑ\80или налог, можда Ñ\82Ñ\80еба да Ñ\81аÑ\87екаÑ\82е неколико минÑ\83Ñ\82а да пÑ\80иÑ\81Ñ\82игне пре него што поново затражите нови код.",
+ "confirmemail_send": "Ð\9fоÑ\88аÑ\99и код за поÑ\82вÑ\80дÑ\83",
"confirmemail_sent": "Потврдна порука је послата.",
- "confirmemail_oncreate": "Ð\9fоÑ\81лаÑ\82 Ñ\98е поÑ\82вÑ\80дни код на вашу имејл адресу.\nОвај код није потребан за пријављивање, али вам треба да бисте укључили могућности имејла на викију.",
+ "confirmemail_oncreate": "Ð\9fоÑ\81лаÑ\82 Ñ\98е код за поÑ\82вÑ\80дÑ\83 на вашу имејл адресу.\nОвај код није потребан за пријављивање, али вам треба да бисте укључили могућности имејла на викију.",
"confirmemail_sendfailed": "{{SITENAME}} не може да пошаље имејл потврду.\nПроверите да ли је имејл адреса правилно написана.\n\nГрешка: $1",
- "confirmemail_invalid": "Ð\9fоÑ\82вÑ\80дни код Ñ\98е неиÑ\81пÑ\80аван. Ð\92еÑ\80оваÑ\82но Ñ\98е истекао.",
- "confirmemail_needlogin": "Морате бити $1 да бисте потврдили имејл адресу.",
- "confirmemail_success": "Ваша имејл адреса је потврђена.\nСада можете да се [[Special:UserLogin|пријавите]] и уживате у викију.",
- "confirmemail_loggedin": "Ваша имејл адреса је сада потврђена.",
- "confirmemail_subject": "{{SITENAME}} – потврда имејл адресе",
- "confirmemail_body": "Неко, вероватно Ви, с IP адресе $1,\nотворио је налог „$2“ с овом имејл адресом на пројекту {{SITENAME}}.\n\nДа потврдите да овај налог стварно припада вама и да активирате\nмогућности имејла на пројекту {{SITENAME}}, отворите ову везу у прегледачу:\n\n$3\n\nУколико налог *не* припада вама, пратите везу\nда откажете потврду имејл адресе:\n\n$5\n\nОвај потврдни код истиче у $4.",
- "confirmemail_body_changed": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и поново активирали могућности имејла, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду имејл адресе:\n\n$5\n\nОвај потврдни код истиче $6 у $7",
- "confirmemail_body_set": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на {{SITENAME}}.\n\nДа бисмо потврдили да овај налог стварно припада вама и поново активирали\nмогућности имејла на {{SITENAME}}, отворите следећу везу у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећу везу да откажете потврду имејл адресе:\n\n$5\n\nОвај потврдни код истиче $4.",
+ "confirmemail_invalid": "Ð\9dеважеÑ\9bи код за поÑ\82вÑ\80дÑ\83.\nÐ\9aод Ñ\98е можда истекао.",
+ "confirmemail_needlogin": "Морате бити $1 да бисте потврдили своју имејл-адресу.",
+ "confirmemail_success": "Ваша имејл-адреса је потврђена.\nСада можете да се [[Special:UserLogin|пријавите]] и уживате у викију.",
+ "confirmemail_loggedin": "Ваша имејл-адреса је сада потврђена.",
+ "confirmemail_subject": "{{SITENAME}} – потврда имејл-адресе",
+ "confirmemail_body": "Неко, вероватно Ви, са IP адресе $1,\nрегистровао је налог „$2“ са овом имејл адресом на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и активирали могућности имејла на пројекту {{SITENAME}}, отворите овај линк у прегледачу:\n\n$3\n\nАко ви *нисте* регистровали налог, пратите овај линк\nда бисте отказали потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче у $4.",
+ "confirmemail_body_changed": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на пројекту {{SITENAME}}.\n\nДа бисте потврдили да овај налог стварно припада вама и поново активирали могућности имејла, отворите следећи линк у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећи линк да откажете потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче $6 у $7",
+ "confirmemail_body_set": "Неко, вероватно Ви, с IP адресе $1,\nпроменио је имејл адресу налога „$2“ у ову адресу на {{SITENAME}}.\n\nДа бисмо потврдили да овај налог стварно припада вама и поново активирали\nмогућности имејла на {{SITENAME}}, отворите следећи линк у прегледачу:\n\n$3\n\nАко налог *не* припада вама, пратите следећи линк да откажете потврду имејл адресе:\n\n$5\n\nОвај код за потврду истиче $4.",
"confirmemail_invalidated": "Потврда имејл адресе је отказана",
"invalidateemail": "Отказивање потврде имејла",
"notificationemail_subject_changed": "Регистрована имејл адреса на пројекту {{SITENAME}} је промењена",
"scarytranscludefailed": "[Добављање шаблона за $1 није успело]",
"scarytranscludefailed-httpstatus": "[Не могу да преузмем шаблон $1: HTTP $2]",
"scarytranscludetoolong": "[URL адреса је предугачка]",
- "deletedwhileediting": "<strong>УпозоÑ\80еÑ\9aе</strong>: Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е обрисана након што сте почели са уређивањем!",
+ "deletedwhileediting": "<strong>УпозоÑ\80еÑ\9aе</strong>: Ð\9eва Ñ\81Ñ\82Ñ\80аниÑ\86а Ñ\98е избрисана након што сте почели са уређивањем!",
"confirmrecreate": "{{GENDER:$1|Корисник|Корисница}} [[User:$1|$1]] ([[User talk:$1|разговор]]) је {{GENDER:$1|обрисао|обрисала}} ову страницу након што сте почели да је уређујете из следећег разлога:\n: <em>$2</em>\nПотврдите да стварно желите да направите страницу.",
"confirmrecreate-noreason": "{{GENDER:$1|Корисник|Корисница}} [[User:$1|$1]] ([[User talk:$1|разговор]]) је {{GENDER:$1|обрисао|обрисала}} ову страницу након што сте почели да је уређујете. Потврдите да стварно желите да поново направите ову страницу.",
"recreate": "Поново направи",
"imgmultigo": "Иди!",
"imgmultigoto": "Иди на страницу $1",
"img-lang-default": "(подразумевани језик)",
- "img-lang-info": "Ð\9fÑ\80икажи овÑ\83 Ñ\81ликÑ\83 на $1. $2",
+ "img-lang-info": "РендеÑ\80Ñ\83Ñ\98 овÑ\83 Ñ\81ликÑ\83 Ñ\83 $1. $2",
"img-lang-go": "Иди",
"ascending_abbrev": "раст.",
"descending_abbrev": "опад.",
"watchlistedit-too-many": "Има превише страница за приказ овде.",
"watchlisttools-clear": "очисти списак надгледања",
"watchlisttools-view": "погледај релевантне промене",
- "watchlisttools-edit": "прикажи и уреди списак надгледања",
+ "watchlisttools-edit": "погледај и уреди списак надгледања",
"watchlisttools-raw": "уреди сиров списак надгледања",
"iranian-calendar-m1": "Фарвардин",
"iranian-calendar-m2": "Ордибехешт",
"specialpages-group-developer": "Програмерске алатке",
"blankpage": "Празна страница",
"intentionallyblankpage": "Ова страница је намерно остављена празном.",
- "external_image_whitelist": " #Ð\9eÑ\81Ñ\82авиÑ\82е оваÑ\98 Ñ\80ед онаквим какав Ñ\98еÑ\81Ñ\82е<pre>\n#Ð\98Ñ\81под додаÑ\98Ñ\82е одломке Ñ\80егÑ\83лаÑ\80ниÑ\85 изÑ\80аза (Ñ\81амо део коÑ\98и Ñ\81е налази измеÑ\92Ñ\83 //)\n#Ð\9eни Ñ\9bе биÑ\82и Ñ\83поÑ\80еÑ\92ени Ñ\81 адÑ\80еÑ\81ама Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81лика\n#Ð\9eне коÑ\98е Ñ\81е поклапаÑ\98Ñ\83 биÑ\9bе пÑ\80иказане као Ñ\81лике, а пÑ\80еоÑ\81Ñ\82але као везе до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте</pre>",
+ "external_image_whitelist": " #Ð\9eÑ\81Ñ\82авиÑ\82е оваÑ\98 Ñ\80ед онаквим какав Ñ\98еÑ\81Ñ\82е<pre>\n#Ð\98Ñ\81под додаÑ\98Ñ\82е одломке Ñ\80егÑ\83лаÑ\80ниÑ\85 изÑ\80аза (Ñ\81амо део коÑ\98и Ñ\81е налази измеÑ\92Ñ\83 //)\n#Ð\9eни Ñ\9bе биÑ\82и Ñ\83поÑ\80еÑ\92ени Ñ\81 адÑ\80еÑ\81ама Ñ\81поÑ\99аÑ\88Ñ\9aиÑ\85 Ñ\81лика\n#Ð\9eне коÑ\98е Ñ\81е поклапаÑ\98Ñ\83 биÑ\9bе пÑ\80иказане као Ñ\81лике, а пÑ\80еоÑ\81Ñ\82але као линкови до слика\n#Редови који почињу с тарабом се сматрају коментарима\n#Сви уноси су осетљиви на мала и велика слова\n\n#Додајте све одломке регуларних израза изнад овог реда. Овај ред не дирајте</pre>",
"tags": "Важеће ознаке промена",
"tag-filter": "Филтер [[Special:Tags|ознака]]:",
"tag-filter-submit": "Филтрирај",
"tags-source-manual": "Ручно је додају корисници и ботови",
"tags-source-none": "Ван употребе",
"tags-edit": "уреди",
- "tags-delete": "обриши",
+ "tags-delete": "избриши",
"tags-activate": "активирај",
"tags-deactivate": "деактивирај",
"tags-hitcount": "$1 {{PLURAL:$1|промена|промене|промена}}",
"tags-create-warnings-below": "Правите нову ознаку, желите ли да наставите?",
"tags-delete-title": "Брисање ознака",
"tags-delete-explanation-initial": "Бришете ознаку „$1“ из базе података.",
- "tags-delete-explanation-warning": "Ова радња је <strong>неповратна</strong> и <strong>не може се поништити</strong>, чак ни администратори базе података је не могу поништити. Будите сигурни да је ово ознака коју желите обрисати.",
+ "tags-delete-explanation-warning": "Ова радња је <strong>неповратна</strong> и <strong>не може да се поништи</strong>. Ово не могу да ураде чак ни администратори базе података. Будите сигурни да је ово ознака коју желите избрисати.",
"tags-delete-reason": "Разлог:",
- "tags-delete-submit": "Ð\9dеповÑ\80аÑ\82но обриши ову ознаку",
+ "tags-delete-submit": "Ð\9dеповÑ\80аÑ\82но избриши ову ознаку",
"tags-delete-not-found": "Ознака „$1“ не постоји.",
- "tags-delete-too-many-uses": "Ð\9eзнака â\80\9e$1â\80\9d Ñ\98е пÑ\80имеÑ\9aена на виÑ\88е од $2 {{PLURAL:$2|Ñ\80евизиÑ\98е|Ñ\80евизиÑ\98а}}, Ñ\88Ñ\82о знаÑ\87и да Ñ\81е не може обрисати.",
+ "tags-delete-too-many-uses": "Ð\9eзнака â\80\9e$1â\80\9d Ñ\98е пÑ\80имеÑ\9aена на виÑ\88е од $2 {{PLURAL:$2|Ñ\80евизиÑ\98е|Ñ\80евизиÑ\98а}}, Ñ\88Ñ\82о знаÑ\87и да Ñ\81е не може избрисати.",
"tags-delete-no-permission": "Немате дозволу да бришете ознаке промена.",
"tags-activate-title": "Активирање ознака",
"tags-activate-question": "Активирате ознаку „$1“.",
"compare-rev1": "Ревизија 1",
"compare-rev2": "Измена 2",
"compare-submit": "Упореди",
- "compare-invalid-title": "Наведени наслов је неисправан.",
+ "compare-invalid-title": "Наслов који сте навели је неважећи.",
"compare-title-not-exists": "Наведени наслов не постоји.",
"compare-revision-not-exists": "Ревизија коју сте навели не постоји.",
"diff-form": "Разлике",
"diff-form-oldid": "ID старе ревизије (опционално)",
"diff-form-revid": "ID измене или разлике",
"diff-form-submit": "Прикажи разлике",
- "permanentlink": "ТÑ\80аÑ\98на веза",
+ "permanentlink": "ТÑ\80аÑ\98ни линк",
"permanentlink-revid": "ID ревизије",
"permanentlink-submit": "Иди на измену",
"dberr-problems": "Дошло је до техничких проблема.",
"logentry-newusers-create": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог",
"logentry-newusers-create2": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3",
"logentry-newusers-byemail": "$1 је {{GENDER:$2|отворио|отворила}} кориснички налог $3 и лозинка је послата на имејл",
- "logentry-newusers-autocreate": "Кориснички налог $1 је аутоматски {{GENDER:$2|отворен}}",
+ "logentry-newusers-autocreate": "$1 је аутоматски {{GENDER:$2|отворио|отворила}} кориснички налог",
"logentry-protect-move_prot": "$1 је {{GENDER:$2|преместио|преместила}} подешавања заштите са $4 на $3",
"logentry-protect-unprotect": "$1 je {{GENDER:$2|скинуо|скинула}} заштиту са странице $3",
"logentry-protect-protect": "$1 је {{GENDER:$2|заштитио|заштитила}} $3 $4",
"logentry-rights-rights-legacy": "$1 је {{GENDER:$2|променио|променила}} чланство групе за $3",
"logentry-rights-autopromote": "$1 је аутоматски {{GENDER:$2|унапређен|унапређена}} из $4 у $5",
"logentry-upload-upload": "$1 је {{GENDER:$2|отпремио|отпремила}} $3",
- "logentry-upload-overwrite": "$1 је {{GENDER:$2|отпремио|отпремила}} нову верзију $3",
+ "logentry-upload-overwrite": "$1 је {{GENDER:$2|отпремио|отпремила}} нову верзију датотеке $3",
"logentry-upload-revert": "$1 је {{GENDER:$2|отпремио|отпремила}} $3",
"log-name-managetags": "Евиденција управљања ознакама",
- "log-description-managetags": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\81е налази Ñ\81пиÑ\81ак измена Ñ\83 вези [[Special:Tags|ознака]]. Ð\95виденÑ\86иÑ\98а Ñ\81адÑ\80жи Ñ\81амо Ñ\80адÑ\9aе коÑ\98е Ñ\81Ñ\83 Ñ\80Ñ\83Ñ\87но извÑ\80Ñ\88или админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80и; Ñ\83ноÑ\81и за ознаке коÑ\98е Ñ\98е напÑ\80авио или обрисао вики софтвера се не налазе у овој евиденцији.",
+ "log-description-managetags": "Ð\9dа овоÑ\98 Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\81е налази Ñ\81пиÑ\81ак измена Ñ\83 вези [[Special:Tags|ознака]]. Ð\95виденÑ\86иÑ\98а Ñ\81адÑ\80жи Ñ\81амо Ñ\80адÑ\9aе коÑ\98е Ñ\81Ñ\83 Ñ\80Ñ\83Ñ\87но извÑ\80Ñ\88или админиÑ\81Ñ\82Ñ\80аÑ\82оÑ\80и; Ñ\83ноÑ\81и за ознаке коÑ\98е Ñ\98е напÑ\80авио или избрисао вики софтвера се не налазе у овој евиденцији.",
"logentry-managetags-create": "$1 је {{GENDER:$2|направио|направила}} ознаку „$4“",
"logentry-managetags-delete": "$1 је {{GENDER:$2|обрисао|обрисала}} ознаку „$4“ (уклоњена је из $5 {{PLURAL:$5|ревизије или уноса у евиденцији|ревизија и/или уноса у евиденцији}})",
"logentry-managetags-activate": "$1 је {{GENDER:$2|активирао|активирала}} ознаку „$4“ за употребу од стране корисника и ботова",
"feedback-thanks-title": "Хвала вам!",
"feedback-useragent": "Кориснички агент:",
"searchsuggest-search": "Претрага",
- "searchsuggest-containing": "садржи...",
- "api-error-badtoken": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа гÑ\80еÑ\88ка: неиÑ\81пÑ\80аван токен.",
+ "searchsuggest-containing": "садржи…",
+ "api-error-badtoken": "УнÑ\83Ñ\82Ñ\80аÑ\88Ñ\9aа гÑ\80еÑ\88ка: лоÑ\88 токен.",
"api-error-emptypage": "Стварање нових празних страница није дозвољено.",
"api-error-publishfailed": "Унутрашња грешка: сервер није успео да објави привремену датотеку.",
"api-error-stashfailed": "Унутрашња грешка: сервер не може да сачува привремену датотеку.",
"default-skin-not-found-row-enabled": "* <code>$1</code> / $2 (омогућена)",
"default-skin-not-found-row-disabled": "* <code>$1</code> / $2 (<strong>онемогућена</strong>)",
"mediastatistics": "Статистика медија",
- "mediastatistics-summary": "СÑ\82аÑ\82иÑ\81Ñ\82ике о Ñ\82иповима поÑ\81лаÑ\82иÑ\85 даÑ\82оÑ\82ека. Ð\9eвде Ñ\81Ñ\83 Ñ\83Ñ\80аÑ\87Ñ\83наÑ\82е Ñ\81амо наÑ\98Ñ\81коÑ\80иÑ\98е веÑ\80зиÑ\98е даÑ\82оÑ\82ека. СÑ\82аÑ\80е или обрисане верзије нису урачунате.",
+ "mediastatistics-summary": "СÑ\82аÑ\82иÑ\81Ñ\82ике о Ñ\82иповима оÑ\82пÑ\80емÑ\99ениÑ\85 даÑ\82оÑ\82ека. Ð\9eвде Ñ\81Ñ\83 Ñ\83Ñ\80аÑ\87Ñ\83наÑ\82е Ñ\81амо наÑ\98Ñ\81коÑ\80иÑ\98е веÑ\80зиÑ\98е даÑ\82оÑ\82ека. СÑ\82аÑ\80е или избрисане верзије нису урачунате.",
"mediastatistics-nbytes": "{{PLURAL:$1|$1 бајт|$1 бајта|$1 бајтова}} ($2; $3%)",
"mediastatistics-bytespertype": "Укупна величина датотеке овог одељка: {{PLURAL:$1|$1 бајт|$1 бајта|$1 бајтова}} ($2; $3%).",
"mediastatistics-allbytes": "Укупна величина свих датотека: {{PLURAL:$1|$1 бајт|$1 бајта|$1 бајтова}} ($2).",
"mediastatistics-header-drawing": "Цртежи (векторске слике)",
"mediastatistics-header-audio": "Аудио",
"mediastatistics-header-video": "Видео",
+ "mediastatistics-header-multimedia": "Обогаћени медији",
"mediastatistics-header-office": "Канцеларија",
"mediastatistics-header-text": "Текстуалне",
"mediastatistics-header-executable": "Извршне",
"json-error-recursion": "Једна или више рекурзивних референци у вредности коју треба енкодирати.",
"json-error-inf-or-nan": "Једна или више NAN или INF вредности у вредности коју треба енкодирати",
"json-error-unsupported-type": "Дата је вредност типа која се не може енкодирати",
- "headline-anchor-title": "Ð\92еза до овог одељка",
+ "headline-anchor-title": "Ð\9bинк до овог одељка",
"special-characters-group-latin": "Латиница",
"special-characters-group-latinextended": "Проширена латиница",
"special-characters-group-ipa": "МФА",
"mw-widgets-mediasearch-noresults": "Нема резултата.",
"mw-widgets-titleinput-description-new-page": "страница још увек не постоји",
"mw-widgets-titleinput-description-redirect": "преусмерава на $1",
- "mw-widgets-categoryselector-add-category-placeholder": "Додај категорију...",
+ "mw-widgets-categoryselector-add-category-placeholder": "Додајте категорију…",
"mw-widgets-usersmultiselect-placeholder": "Додај још...",
"date-range-from": "Од датума:",
"date-range-to": "До датума:",
"authmanager-authplugin-setpass-failed-message": "Додатак за потврду идентитета је одбио промену лозинке.",
"authmanager-authplugin-create-fail": "Додатак за потврду идентитета је одбио прављење налога.",
"authmanager-authplugin-setpass-denied": "Додатак за потврду идентитета не дозвољава мењање лозику.",
- "authmanager-authplugin-setpass-bad-domain": "Ð\9dеиÑ\81пÑ\80аван домен.",
+ "authmanager-authplugin-setpass-bad-domain": "Ð\9dеважеÑ\9bи домен.",
"authmanager-autocreate-noperm": "Аутоматско прављење налога није дозвољено.",
"authmanager-userdoesnotexist": "Кориснички налог „$1“ није отворен.",
"authmanager-username-help": "Корисничко име за потврду идентитета.",
"authform-wrongtoken": "Погрешан токен",
"specialpage-securitylevel-not-allowed-title": "Није дозвољено",
"specialpage-securitylevel-not-allowed": "Жао нам је, није вам дозвољено да користите ову страницу јер не могу да потврдим ваш идентитет.",
- "authpage-cannot-login": "Ð\9dе могÑ\83 запоÑ\87еÑ\82и пријаву.",
+ "authpage-cannot-login": "Ð\9dе могÑ\83 да запоÑ\87нем пријаву.",
"authpage-cannot-login-continue": "Не могу да наставим са пријавом. Ваша сесија је највероватније истекла.",
- "authpage-cannot-create": "Ð\9dе могÑ\83 запоÑ\87еÑ\82и Ñ\81тварање налога.",
- "authpage-cannot-link": "Ð\9dе могÑ\83 запоÑ\87еÑ\82и Ñ\81паÑ\98ање налога.",
+ "authpage-cannot-create": "Ð\9dе могÑ\83 да запоÑ\87нем отварање налога.",
+ "authpage-cannot-link": "Ð\9dе могÑ\83 да запоÑ\87нем повезивање налога.",
"cannotauth-not-allowed-title": "Приступ је одбијен",
"cannotauth-not-allowed": "Није вам дозвољено да користите ову страницу",
"changecredentials": "Промена акредитива",
"pageid": "ID странице: $1",
"rawhtml-notallowed": "<html> тагови не могу да се користе ван нормалних страница.",
"gotointerwiki": "Напуштање пројекта {{SITENAME}}",
- "gotointerwiki-invalid": "Ð\9eдабÑ\80ани наслов је невалидан.",
+ "gotointerwiki-invalid": "Ð\9dаведени наслов је невалидан.",
"gotointerwiki-external": "Управо ћете да напустите пројекат {{SITENAME}} да бисте на засебном веб-сајту посетили [[$2]].\n\n'''[$1 Продужи на $1]'''",
"undelete-cantedit": "Не можете повратити ову страницу јер немате дозволу да је уређујете.",
"undelete-cantcreate": "Не можете повратити ову страницу јер нема постојеће странице са овим именом и немате дозволу да направите ову страницу.",
"pagedata-title": "Подаци странице",
"pagedata-not-acceptable": "Није пронађен одговарајући облик. Подржане MIME-врсте: $1",
- "pagedata-bad-title": "Ð\9dеважеÑ\9bи наслов: $1.",
+ "pagedata-bad-title": "Ð\9dевалидан наслов: $1.",
"passwordpolicies": "Правила за лозинке",
"passwordpolicies-group": "Група",
"passwordpolicies-policies": "Правила",
"magiclink-tracking-isbn": "使用 ISBN 魔法連結的頁面",
"magiclink-tracking-isbn-desc": "此頁面使用 ISBN 魔法連結的頁面,請參考 [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Magic_links mediawiki.org] 的如何遷移。",
"specialloguserlabel": "執行者:",
- "speciallogtitlelabel": "目標 (標題或以 {{ns:user}}:使用者 表示使用者):",
+ "speciallogtitlelabel": "目標(標題或以 {{ns:user}}:使用者名稱 表示使用者):",
"log": "日誌",
"logeventslist-submit": "顯示",
"logeventslist-more-filters": "顯示額外日誌:",
--- /dev/null
+<?php
+
+/**
+ * Adds a change tag to the wiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Adds a change tag to the wiki
+ *
+ * @ingroup Maintenance
+ * @since 1.32
+ */
+class AddChangeTag extends Maintenance {
+
+ public function __construct() {
+ parent::__construct();
+ $this->addDescription( 'Adds a change tag to the wiki.' );
+
+ $this->addOption( 'tag', 'Tag to add', true, true );
+ $this->addOption( 'reason', 'Reason for adding the tag', true, true );
+ }
+
+ public function execute() {
+ $user = User::newSystemUser( 'Maintenance script', [ 'steal' => true ] );
+
+ $tag = $this->getOption( 'tag' );
+
+ $status = ChangeTags::createTagWithChecks(
+ $tag,
+ $this->getOption( 'reason' ),
+ $user
+ );
+
+ if ( !$status->isGood() ) {
+ $this->fatalError( $status->getWikiText( null, null, 'en' ) );
+ }
+
+ $this->output( "$tag was created.\n" );
+ }
+}
+
+$maintClass = AddChangeTag::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
// Run benchmarks
$stat = new RunningStat();
for ( $i = 0; $i < $count; $i++ ) {
+ // Setup outside of time measure for each loop
+ if ( isset( $bench['setupEach'] ) ) {
+ $bench['setupEach']();
+ }
$t = microtime( true );
call_user_func_array( $bench['function'], $bench['args'] );
$t = ( microtime( true ) - $t ) * 1000;
[
'function' => [ $this, 'getPrefixedTextTitle' ],
],
- [
+ 'parseTitleValue cached' => [
'function' => [ $this, 'parseTitleValue' ],
'setup' => [ $this, 'randomize' ],
],
- [
+ 'parseTitle cached' => [
'function' => [ $this, 'parseTitle' ],
'setup' => [ $this, 'randomize' ],
],
+ 'parseTitleValue no cache' => [
+ 'function' => [ $this, 'parseTitleValue' ],
+ 'setupEach' => [ $this, 'randomize' ],
+ ],
+ 'parseTitle no cache' => [
+ 'function' => [ $this, 'parseTitle' ],
+ 'setupEach' => [ $this, 'randomize' ],
+ ],
] );
}
$this->handleMoves( $dbr, $output );
// We need to handle restores too since delete may have happened in previous update.
$this->handleRestores( $dbr, $output );
+ // Process newly added pages
$this->handleAdds( $dbr, $output );
- $this->handleChanges( $dbr, $output );
+ // Process page edits
+ $this->handleEdits( $dbr, $output );
+ // Process categorization changes
+ $this->handleCategorization( $dbr, $output );
// Update timestamp
fwrite( $output, $this->updateTS( $this->endTS ) );
}
/**
- * Write data for a set of categories
+ * Write parent data for a set of categories.
+ * The list has the child categories.
* @param IDatabase $dbr
- * @param string[] $pages List of categories: id => title
+ * @param string[] $pages List of child categories: id => title
*/
private function writeParentCategories( IDatabase $dbr, $pages ) {
foreach ( $this->getCategoryLinksIterator( $dbr, array_keys( $pages ) ) as $row ) {
}
/**
- * Fetch categorization changes
+ * Fetch categorization changes or edits
* @param IDatabase $dbr
* @return BatchRowIterator
*/
- protected function getChangedCatsIterator( IDatabase $dbr ) {
- $it = $this->setupChangesIterator( $dbr );
+ protected function getChangedCatsIterator( IDatabase $dbr, $type ) {
+ $it =
+ $this->setupChangesIterator( $dbr );
$it->addConditions( [
'rc_namespace' => NS_CATEGORY,
'rc_new' => 0,
- 'rc_type' => [ RC_EDIT, RC_CATEGORIZE ],
+ 'rc_type' => $type,
] );
$this->addIndex( $it );
return $it;
}
/**
+ * Handle edits for category texts
* @param IDatabase $dbr
* @param resource $output
*/
- public function handleChanges( IDatabase $dbr, $output ) {
- foreach ( $this->getChangedCatsIterator( $dbr ) as $batch ) {
+ public function handleEdits( IDatabase $dbr, $output ) {
+ // Editing category can change hidden flag and add new parents.
+ // TODO: it's pretty expensive to update all edited categories, and most edits
+ // aren't actually interesting for us. Some way to know which are interesting?
+ // We can capture recategorization on the next step, but not change in hidden status.
+ foreach ( $this->getChangedCatsIterator( $dbr, RC_EDIT ) as $batch ) {
$pages = [];
$deleteUrls = [];
foreach ( $batch as $row ) {
+ // Note that on categorization event, cur_id points to
+ // the child page, not the parent category!
if ( isset( $this->processed[$row->rc_cur_id] ) ) {
// We already captured this one before
continue;
$deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
}
+ fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Edits" ) );
+ }
+ }
+
+ /**
+ * Handles categorization changes
+ * @param IDatabase $dbr
+ * @param resource $output
+ */
+ public function handleCategorization( IDatabase $dbr, $output ) {
+ $processedTitle = [];
+ // Categorization change can add new parents and change counts
+ // for the parent category.
+ foreach ( $this->getChangedCatsIterator( $dbr, RC_CATEGORIZE ) as $batch ) {
+ /*
+ * Note that on categorization event, cur_id points to
+ * the child page, not the parent category!
+ * So we need to have a two-stage process, since we have ID from one
+ * category and title from another, and we need both for proper updates.
+ * TODO: For now, we do full update even though some data hasn't changed,
+ * e.g. parents for parent cat and counts for child cat.
+ */
+ foreach ( $batch as $row ) {
+ $childPages[$row->rc_cur_id] = true;
+ $parentCats[$row->rc_title] = true;
+ }
+
+ $joinConditions = [
+ 'page_props' => [
+ 'LEFT JOIN',
+ [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ],
+ ],
+ 'category' => [
+ 'LEFT JOIN',
+ [ 'cat_title = page_title' ],
+ ],
+ ];
+
+ $pages = [];
+ $deleteUrls = [];
+
+ if ( !empty( $childPages ) ) {
+ // Load child rows by ID
+ $childRows = $dbr->select(
+ [ 'page', 'page_props', 'category' ],
+ [
+ 'page_id',
+ 'rc_title' => 'page_title',
+ 'pp_propname',
+ 'cat_pages',
+ 'cat_subcats',
+ 'cat_files',
+ ],
+ [ 'page_namespace' => NS_CATEGORY, 'page_id' => array_keys( $childPages ) ],
+ __METHOD__,
+ [],
+ $joinConditions
+ );
+ foreach ( $childRows as $row ) {
+ if ( isset( $this->processed[$row->page_id] ) ) {
+ // We already captured this one before
+ continue;
+ }
+ $this->writeCategoryData( $row );
+ $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
+ $this->processed[$row->page_id] = true;
+ }
+ }
+
+ if ( !empty( $parentCats ) ) {
+ // Load parent rows by title
+ $joinConditions = [
+ 'page' => [
+ 'LEFT JOIN',
+ [ 'page_title = cat_title', 'page_namespace' => NS_CATEGORY ],
+ ],
+ 'page_props' => [
+ 'LEFT JOIN',
+ [ 'pp_propname' => 'hiddencat', 'pp_page = page_id' ],
+ ],
+ ];
+
+ $parentRows = $dbr->select(
+ [ 'category', 'page', 'page_props' ],
+ [
+ 'page_id',
+ 'rc_title' => 'cat_title',
+ 'pp_propname',
+ 'cat_pages',
+ 'cat_subcats',
+ 'cat_files',
+ ],
+ [ 'cat_title' => array_keys( $parentCats ) ],
+ __METHOD__,
+ [],
+ $joinConditions
+ );
+ foreach ( $parentRows as $row ) {
+ if ( $row->page_id && isset( $this->processed[$row->page_id] ) ) {
+ // We already captured this one before
+ continue;
+ }
+ if ( isset( $processedTitle[$row->rc_title] ) ) {
+ // We already captured this one before
+ continue;
+ }
+ $this->writeCategoryData( $row );
+ $deleteUrls[] = '<' . $this->categoriesRdf->labelToUrl( $row->rc_title ) . '>';
+ if ( $row->page_id ) {
+ $this->processed[$row->page_id] = true;
+ }
+ $processedTitle[$row->rc_title] = true;
+ }
+ }
+
fwrite( $output, $this->getCategoriesUpdate( $dbr, $deleteUrls, $pages, "Changes" ) );
}
}
密执安 密歇根
密西根 密歇根
紐澤西 新泽西
+奧勒岡 俄勒冈
蒙特婁 蒙特利尔
千里達及托巴哥 特立尼达和多巴哥
千里達 特立尼达
主機板 主板
網際網絡 互联网
原始碼 源代码
-螢幕 屏幕
螢屏 荧屏
解像度 分辨率
IP位址 IP地址
希拉莉 希拉里
文翠珊 特蕾莎·梅
德蕾莎·梅伊 特蕾莎·梅
+馬拉度納 马拉多纳
+馬勒當拿 马拉多纳
麻薩諸塞 马萨诸塞
東南亞國家協會 东南亚国家联盟
獨立國協 独联体
烏龍麵 乌冬面
披索 比索
真人騷 真人秀
+帕運會 残奥会
+帕拉林匹克 残疾人奥林匹克
+傷殘奧林匹克 残疾人奥林匹克
愛荷華 艾奧瓦
爱荷华 艾奧瓦
得克萨斯 德克薩斯
+奧勒岡 俄勒岡
蒙特婁 蒙特利爾
紐賓士域 紐賓士域
加泰隆尼亞 加泰羅尼亞
链接 連結
分辨率 解像度
解析度 解像度
+服务器 伺服器
智慧卡 智能卡
晶元 晶片
芯片 晶片
晶体管 電晶體
源代码 原始碼
IP地址 IP位址
-屏幕 螢幕
荧屏 螢屏
版权信息 版權資訊
信息时代 資訊時代
翁山蘇姬 昂山素姬
德蕾莎·梅伊 文翠珊
特蕾莎·梅 文翠珊
+马拉多纳 馬勒當拿
+馬拉度納 馬勒當拿
西洋棋 國際象棋
隐私 私隱
隱私 私隱
塑料袋 膠袋
烏龍麵 烏冬麵
真人秀 真人騷
+帕運會 殘奧會
+帕拉林匹克 傷殘奧林匹克
+残疾人奥林匹克 傷殘奧林匹克
曾运乾 曾运乾
乾贵士 乾贵士
乾东 乾东
+乾顺 乾顺
柳诒徵 柳诒徵
於夫罗 於夫罗
於梨华 於梨华
觔斗 斤斗
穀阳 穀阳
伊東豊雄 伊东丰雄
+峯會 峰会
+頂峯 顶峰
+巔峯 巅峰
+拚弃 拚弃
+拚棄 拚弃
+拚却 拚却
+拚卻 拚却
+满拚自尽 满拚自尽
+滿拚自盡 满拚自尽
+拚生尽死 拚生尽死
+拚生盡死 拚生尽死
晶体管 電晶體
IP地址 IP位址
解像度 解析度
-屏幕 螢幕
荧屏 螢屏
版权信息 版權資訊
航天器 太空飛行器
格莱美奖 葛萊美獎
乔布斯 賈伯斯
波里活 寶萊塢
+马拉多纳 馬拉度納
+馬勒當拿 馬拉度納
库尔德族 庫德族
库尔德人 庫德人
行人路 人行道
触摸屏 觸控螢幕
乌冬面 烏龍麵
真人騷 真人秀
+残奥会 帕運會
+殘奧會 帕運會
+残疾人奥林匹克 帕拉林匹克
+傷殘奧林匹克 帕拉林匹克
‘ 『
’ 』
’s ’s
+’m ’m
+’t ’t
+’re ’re
手塚治虫 手塚治虫
寇仇 寇讎
往日无仇 往日無讎
簡筑翎 簡筑翎
楊雅筑 楊雅筑
尸羅精舍 尸羅精舍
+拚舍 拚捨
騰格里 騰格里
進制 進制
強制 強制
U+05563啣|U+08854衔|
U+055AB喫|U+05403吃|
U+055C1嗁|U+0557C啼|
-U+05605嘅|U+06168慨|
U+05611嘑|U+0547C呼|
U+05620嘠|U+0560E嘎|
U+05637嘷|U+055E5嗥|
出乖弄醜
出乖露醜
獲匪其醜
+長得醜
乙丑
丁丑
己丑
括髮
髡髮
鵠髮
-截髮
解髮佯狂
淨髮
噙齒戴髮
犖确
磽确
确瘠
-拚捨
廣捨
齊王捨牛
捨墮
這麼幹
幹這
幹仗
+包幹
李連杰
周杰
杰倫
挌鬥
好鬥
鬥合
-æ\8b\9a鬥
+æ\8b¼鬥
兩虎共鬥
兩鼠鬥穴
鬥犀臺
穩健的台風
台風獎
電視台風
+舞台風格
足球台
網球台
合府上
溫洛克期
科尼亞克期
馬斯垂克期
-滿拚自盡
-拚生盡死
-拚卻
-拚老命
-拚絕
成於思
單單於
名單於
繫鞋帶
繫船
繫着
-重回
+重回 #分詞
+收回
挑大樑
扛大樑
后豐
帝后臺
紅后假說
尊后
+后姓
+電影後
+封為后
+天神之后
+夏后氏
前往
新井里美
樗里子
湧水
高涌泉
涌水塘
-后姓
計劃
党姓
党家
煙臺
太醜
御製
-電影後
-封為后
皮托管
白面包青天
-天神之后
你誇
誇你
誇我
自誇
誇稱
誇讚
+像讚
黎克特制
筆桿
袋桿
--- /dev/null
+### Format of this file
+#
+# The top-level keys are module names (as registered in Resources.php).
+# The values of these keys are resource descriptors.
+#
+# In each resource descriptor object, the `src` and `integrity` keys are required.
+#
+# * `src`: Full URL to a remote resource.
+# * `integrity`: Cryptographic hash used to verify the remote content.
+# Uses the "integrity metadata" format defined at <https://www.w3.org/TR/SRI/>.
+# * `dest`: An object mapping paths from the remote resource to a destination in
+# `/resources/lib/$module/`. The value may be omitted to indicate that
+# paths should be extracted to the destination directory itself.
+oojs:
+ src: https://registry.npmjs.org/oojs/-/oojs-2.2.2.tgz
+ integrity: sha256-ebgQW2EGrSkBCnDJBGqDpsBDjA3PMN/M8U5DyLHt9mw=
+ dest:
+ package/dist/oojs.jquery.js:
+ package/AUTHORS.txt:
+ package/LICENSE-MIT:
+ package/README.md:
+oojs-ui:
+ src: https://registry.npmjs.org/oojs-ui/-/oojs-ui-0.28.0.tgz
+ integrity: sha384-j8bzlCPrfS4sca+U9JO9tdcewDlLlDlOVOsLn+Vqlcg5GU59vLSd7TVm4FiuTowy
+ dest:
+ # Main stuff
+ package/dist/oojs-ui-core.js{,.map.json}:
+ package/dist/oojs-ui-core-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-widgets.js{,.map.json}:
+ package/dist/oojs-ui-widgets-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-toolbars.js{,.map.json}:
+ package/dist/oojs-ui-toolbars-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-windows.js{,.map.json}:
+ package/dist/oojs-ui-windows-{wikimediaui,apex}.css:
+ package/dist/oojs-ui-{wikimediaui,apex}.js{,.map.json}:
+ package/dist/i18n:
+ package/dist/images:
+ # WikimediaUI theme
+ package/dist/themes/wikimediaui/images/icons/*.{svg,png}: themes/wikimediaui/images/icons
+ package/dist/themes/wikimediaui/images/indicators/*.{svg,png}: themes/wikimediaui/images/indicators
+ package/dist/themes/wikimediaui/images/textures/*.{gif,svg}: themes/wikimediaui/images/textures
+ package/src/themes/wikimediaui/*.json: themes/wikimediaui
+ package/dist/wikimedia-ui-base.less:
+ # Apex theme (icons, indicators, and textures)
+ package/src/themes/apex/*.json: themes/apex
+ # Misc stuff
+ package/dist/AUTHORS.txt:
+ package/dist/History.md:
+ package/dist/LICENSE-MIT:
+ package/dist/README.md:
--- /dev/null
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+require_once __DIR__ . '/../Maintenance.php';
+
+/**
+ * Manage foreign resources registered with ResourceLoader.
+ *
+ * @ingroup Maintenance
+ * @since 1.32
+ */
+class ManageForeignResources extends Maintenance {
+ private $defaultAlgo = 'sha384';
+ private $tmpParentDir;
+
+ public function __construct() {
+ global $IP;
+ parent::__construct();
+ $this->addDescription( <<<TEXT
+Manage foreign resources registered with ResourceLoader.
+
+This helps developers to download, verify and update local copies of upstream
+libraries registered as ResourceLoader modules. See also foreign-resources.yaml.
+
+For sources that don't publish an integrity hash, leave the value empty at
+first, and run this script with --make-sri to compute the hashes.
+
+This script runs in dry mode by default. Use --update to actually change, remove,
+or add files to /resources/lib/.
+TEXT
+ );
+ $this->addArg( 'module', 'Name of a single module (Default: all)', false );
+ $this->addOption( 'update', ' resources/lib/ missing integrity metadata' );
+ $this->addOption( 'make-sri', 'Compute missing integrity metadata' );
+ $this->addOption( 'verbose', 'Be verbose' );
+
+ // Use a directory in $IP instead of wfTempDir() because
+ // PHP's rename() does not work across file systems.
+ $this->tmpParentDir = "{$IP}/resources/tmp";
+ }
+
+ public function execute() {
+ global $IP;
+ $module = $this->getArg();
+ $makeSRI = $this->hasOption( 'make-sri' );
+
+ $registry = $this->parseBasicYaml(
+ file_get_contents( __DIR__ . '/foreign-resources.yaml' )
+ );
+ foreach ( $registry as $moduleName => $info ) {
+ if ( $module !== null && $moduleName !== $module ) {
+ continue;
+ }
+ $this->verbose( "\n### {$moduleName}\n\n" );
+
+ // Validate required keys
+ $info += [ 'src' => null, 'integrity' => null, 'dest' => null ];
+ if ( $info['src'] === null ) {
+ $this->fatalError( "Module '$moduleName' must have a 'src' key." );
+ }
+ $integrity = is_string( $info['integrity'] ) ? $info['integrity'] : $makeSRI;
+ if ( $integrity === false ) {
+ $this->fatalError( "Module '$moduleName' must have an 'integrity' key." );
+ }
+
+ // Download the resource
+ $data = Http::get( $info['src'], [ 'followRedirects' => false ] );
+ if ( $data === false ) {
+ $this->fatalError( "Failed to download resource for '$moduleName'." );
+ }
+
+ // Validate integrity metadata
+ $this->output( "... checking integrity of '{$moduleName}'\n" );
+ $algo = $integrity === true ? $this->defaultAlgo : explode( '-', $integrity )[0];
+ $actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
+ if ( $integrity === true ) {
+ $this->output( "Integrity for '{$moduleName}':\n\t${actualIntegrity}\n" );
+ continue;
+ } elseif ( $integrity !== $actualIntegrity ) {
+ $this->fatalError( "Integrity check failed for '{$moduleName}:\n" .
+ "Expected: {$integrity}\n" .
+ "Actual: {$actualIntegrity}"
+ );
+ }
+
+ // Determine destination
+ $destDir = "{$IP}/resources/lib/$moduleName";
+ $this->output( "... extracting files for '{$moduleName}'\n" );
+ $this->handleTypeTar( $moduleName, $data, $destDir, $info );
+ }
+
+ // Clean up
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ $this->output( "\nDone!\n" );
+ }
+
+ private function handleTypeTar( $moduleName, $data, $destDir, array $info ) {
+ global $IP;
+ wfRecursiveRemoveDir( $this->tmpParentDir );
+ if ( !wfMkdirParents( $this->tmpParentDir ) ) {
+ $this->fatalError( "Unable to create {$this->tmpParentDir}" );
+ }
+
+ // Write resource to temporary file and open it
+ $tmpFile = "{$this->tmpParentDir}/$moduleName.tar";
+ $this->verbose( "... writing '$moduleName' src to $tmpFile\n" );
+ file_put_contents( $tmpFile, $data );
+ $p = new PharData( $tmpFile );
+ $tmpDir = "{$this->tmpParentDir}/$moduleName";
+ $p->extractTo( $tmpDir );
+ unset( $data, $p );
+
+ if ( $info['dest'] === null ) {
+ // Replace the entire directory as-is
+ if ( !$this->hasOption( 'update' ) ) {
+ $this->output( "[dry run] Would replace /resources/lib/$moduleName\n" );
+ } else {
+ wfRecursiveRemoveDir( $destDir );
+ if ( !rename( $tmpDir, $destDir ) ) {
+ $this->fatalError( "Could not move $destDir to $tmpDir." );
+ }
+ }
+ return;
+ }
+
+ // Create and/or empty the destination
+ if ( !$this->hasOption( 'update' ) ) {
+ $this->output( "... [dry run] would empty /resources/lib/$moduleName\n" );
+ } else {
+ wfRecursiveRemoveDir( $destDir );
+ wfMkdirParents( $destDir );
+ }
+
+ // Expand and normalise the 'dest' entries
+ $toCopy = [];
+ foreach ( $info['dest'] as $fromSubPath => $toSubPath ) {
+ // Use glob() to expand wildcards and check existence
+ $fromPaths = glob( "{$tmpDir}/{$fromSubPath}", GLOB_BRACE );
+ if ( !$fromPaths ) {
+ $this->fatalError( "Path '$fromSubPath' of '$moduleName' not found." );
+ }
+ foreach ( $fromPaths as $fromPath ) {
+ $toCopy[$fromPath] = $toSubPath === null
+ ? "$destDir/" . basename( $fromPath )
+ : "$destDir/$toSubPath/" . basename( $fromPath );
+ }
+ }
+ foreach ( $toCopy as $from => $to ) {
+ if ( !$this->hasOption( 'update' ) ) {
+ $shortFrom = strtr( $from, [ "$tmpDir/" => '' ] );
+ $shortTo = strtr( $to, [ "$IP/" => '' ] );
+ $this->output( "... [dry run] would move $shortFrom to $shortTo\n" );
+ } else {
+ $this->verbose( "... moving $from to $to\n" );
+ wfMkdirParents( dirname( $to ) );
+ if ( !rename( $from, $to ) ) {
+ $this->fatalError( "Could not move $from to $to." );
+ }
+ }
+ }
+ }
+
+ private function verbose( $text ) {
+ if ( $this->hasOption( 'verbose' ) ) {
+ $this->output( $text );
+ }
+ }
+
+ /**
+ * Basic YAML parser.
+ *
+ * Supports only string or object values, and 2 spaces indentation.
+ *
+ * @todo Just ship symfony/yaml.
+ * @param string $input
+ * @return array
+ */
+ private function parseBasicYaml( $input ) {
+ $lines = explode( "\n", $input );
+ $root = [];
+ $stack = [ &$root ];
+ $prev = 0;
+ foreach ( $lines as $i => $text ) {
+ $line = $i + 1;
+ $trimmed = ltrim( $text, ' ' );
+ if ( $trimmed === '' || $trimmed[0] === '#' ) {
+ continue;
+ }
+ $indent = strlen( $text ) - strlen( $trimmed );
+ if ( $indent % 2 !== 0 ) {
+ throw new Exception( __METHOD__ . ": Odd indentation on line $line." );
+ }
+ $depth = $indent === 0 ? 0 : ( $indent / 2 );
+ if ( $depth < $prev ) {
+ // Close previous branches we can't re-enter
+ array_splice( $stack, $depth + 1 );
+ }
+ if ( !array_key_exists( $depth, $stack ) ) {
+ throw new Exception( __METHOD__ . ": Too much indentation on line $line." );
+ }
+ if ( strpos( $trimmed, ':' ) === false ) {
+ throw new Exception( __METHOD__ . ": Missing colon on line $line." );
+ }
+ $dest =& $stack[ $depth ];
+ if ( $dest === null ) {
+ // Promote from null to object
+ $dest = [];
+ }
+ list( $key, $val ) = explode( ':', $trimmed, 2 );
+ $val = ltrim( $val, ' ' );
+ if ( $val !== '' ) {
+ // Add string
+ $dest[ $key ] = $val;
+ } else {
+ // Add null (may become an object later)
+ $val = null;
+ $stack[] = &$val;
+ $dest[ $key ] = &$val;
+ }
+ $prev = $depth;
+ unset( $dest, $val );
+ }
+ return $root;
+ }
+}
+
+$maintClass = ManageForeignResources::class;
+require_once RUN_MAINTENANCE_IF_MAIN;
+++ /dev/null
-#!/bin/bash -eu
-
-# This script generates a commit that updates our copy of OOjs
-
-if [ -n "${2:-}" ]
-then
- # Too many parameters
- echo >&2 "Usage: $0 [<version>]"
- exit 1
-fi
-
-REPO_DIR=$(cd "$(dirname $0)/../.."; pwd) # Root dir of the git repo working tree
-TARGET_DIR="resources/lib/oojs" # Destination relative to the root of the repo
-NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-oojs') # e.g. /tmp/update-oojs.rI0I5Vir
-
-# Prepare working tree
-cd "$REPO_DIR"
-git reset -- $TARGET_DIR
-git checkout -- $TARGET_DIR
-git fetch origin
-git checkout -B upstream-oojs origin/master
-
-# Fetch upstream version
-cd $NPM_DIR
-if [ -n "${1:-}" ]
-then
- npm install "oojs@$1"
-else
- npm install oojs
-fi
-
-OOJS_VERSION=$(node -e 'console.log(require("./node_modules/oojs/package.json").version);')
-if [ "$OOJS_VERSION" == "" ]
-then
- echo 'Could not find OOjs version'
- exit 1
-fi
-
-# Copy file(s)
-rsync --force ./node_modules/oojs/dist/oojs.jquery.js "$REPO_DIR/$TARGET_DIR"
-rsync --force ./node_modules/oojs/AUTHORS.txt "$REPO_DIR/$TARGET_DIR"
-rsync --force ./node_modules/oojs/LICENSE-MIT "$REPO_DIR/$TARGET_DIR"
-rsync --force ./node_modules/oojs/README.md "$REPO_DIR/$TARGET_DIR"
-
-# Clean up temporary area
-rm -rf "$NPM_DIR"
-
-# Generate commit
-cd $REPO_DIR
-
-COMMITMSG=$(cat <<END
-Update OOjs to v$OOJS_VERSION
-
-Release notes:
- https://gerrit.wikimedia.org/r/plugins/gitiles/oojs/core/+/v$OOJS_VERSION/History.md
-END
-)
-
-# Stage deletion, modification and creation of files. Then commit.
-git add --update $TARGET_DIR
-git add $TARGET_DIR
-git commit -m "$COMMITMSG"
+++ /dev/null
-#!/bin/bash -eu
-
-# This script generates a commit that updates our copy of OOUI
-
-if [ -n "${2:-}" ]
-then
- # Too many parameters
- echo >&2 "Usage: $0 [<version>]"
- exit 1
-fi
-
-REPO_DIR=$(cd "$(dirname $0)/../.."; pwd) # Root dir of the git repo working tree
-TARGET_DIR="resources/lib/oojs-ui" # Destination relative to the root of the repo
-NPM_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'update-ooui') # e.g. /tmp/update-ooui.rI0I5Vir
-
-# Prepare working tree
-cd "$REPO_DIR"
-git reset composer.json
-git checkout composer.json
-git reset -- $TARGET_DIR
-git checkout -- $TARGET_DIR
-git fetch origin
-git checkout -B upstream-ooui origin/master
-
-# Fetch upstream version
-cd $NPM_DIR
-if [ -n "${1:-}" ]
-then
- npm install "oojs-ui@$1"
-else
- npm install oojs-ui
-fi
-
-OOUI_VERSION=$(node -e 'console.log(require("./node_modules/oojs-ui/package.json").version);')
-if [ "$OOUI_VERSION" == "" ]
-then
- echo 'Could not find OOUI version'
- exit 1
-fi
-
-# Copy files, picking the necessary ones from source and distribution
-rm -r "$REPO_DIR/$TARGET_DIR"
-
-# Core and thematic code and styling
-mkdir -p "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-core.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-core-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-widgets.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-widgets-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-toolbars.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-toolbars-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-windows.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-windows-{wikimediaui,apex}.css "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/oojs-ui-{wikimediaui,apex}.js{,.map.json} "$REPO_DIR/$TARGET_DIR"
-
-# i18n
-mkdir -p "$REPO_DIR/$TARGET_DIR/i18n"
-cp -R ./node_modules/oojs-ui/dist/i18n "$REPO_DIR/$TARGET_DIR"
-
-# Core images (currently two .cur files)
-mkdir -p "$REPO_DIR/$TARGET_DIR/images"
-cp -R ./node_modules/oojs-ui/dist/images "$REPO_DIR/$TARGET_DIR"
-
-# WikimediaUI theme icons, indicators, and textures
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/icons"
-cp ./node_modules/oojs-ui/dist/themes/wikimediaui/images/icons/*.{svg,png} "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/icons"
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/indicators"
-cp ./node_modules/oojs-ui/dist/themes/wikimediaui/images/indicators/*.{svg,png} "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/indicators"
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/textures"
-cp ./node_modules/oojs-ui/dist/themes/wikimediaui/images/textures/*.{gif,svg} "$REPO_DIR/$TARGET_DIR/themes/wikimediaui/images/textures"
-
-cp ./node_modules/oojs-ui/src/themes/wikimediaui/*.json "$REPO_DIR/$TARGET_DIR/themes/wikimediaui"
-
-# Apex theme icons, indicators, and textures
-mkdir -p "$REPO_DIR/$TARGET_DIR/themes/apex"
-cp ./node_modules/oojs-ui/src/themes/apex/*.json "$REPO_DIR/$TARGET_DIR/themes/apex"
-
-# WikimediaUI LESS variables for sharing
-cp ./node_modules/oojs-ui/dist/wikimedia-ui-base.less "$REPO_DIR/$TARGET_DIR"
-
-# Misc stuff
-cp ./node_modules/oojs-ui/dist/AUTHORS.txt "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/History.md "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/LICENSE-MIT "$REPO_DIR/$TARGET_DIR"
-cp ./node_modules/oojs-ui/dist/README.md "$REPO_DIR/$TARGET_DIR"
-
-# Clean up temporary area
-rm -rf "$NPM_DIR"
-
-# Generate commit
-cd $REPO_DIR
-
-COMMITMSG=$(cat <<END
-Update OOUI to v$OOUI_VERSION
-
-Release notes:
- https://phabricator.wikimedia.org/diffusion/GOJU/browse/master/History.md;v$OOUI_VERSION
-END
-)
-
-# Update composer.json as well
-composer require oojs/oojs-ui $OOUI_VERSION --no-update
-
-# Stage deletion, modification and creation of files. Then commit.
-git add --update $TARGET_DIR
-git add $TARGET_DIR
-git add composer.json
-git commit -m "$COMMITMSG"
'targets' => [ 'mobile', 'desktop' ],
],
'jquery.async' => [
- 'scripts' => 'resources/lib/jquery/jquery.async.js',
+ 'scripts' => 'resources/lib/jquery.async.js',
],
'jquery.byteLength' => [
'scripts' => 'resources/src/jquery/jquery.byteLength.js',
'dependencies' => 'mediawiki.jqueryMsg',
],
'jquery.cookie' => [
- 'scripts' => 'resources/lib/jquery/jquery.cookie.js',
+ 'scripts' => 'resources/lib/jquery.cookie.js',
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.form' => [
- 'scripts' => 'resources/lib/jquery/jquery.form.js',
+ 'scripts' => 'resources/lib/jquery.form.js',
],
'jquery.fullscreen' => [
- 'scripts' => 'resources/lib/jquery/jquery.fullscreen.js',
+ 'scripts' => 'resources/lib/jquery.fullscreen.js',
],
'jquery.getAttrs' => [
'targets' => [ 'desktop', 'mobile' ],
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.hoverIntent' => [
- 'scripts' => 'resources/lib/jquery/jquery.hoverIntent.js',
+ 'scripts' => 'resources/lib/jquery.hoverIntent.js',
],
'jquery.i18n' => [
'scripts' => [
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.mockjax' => [
- 'scripts' => 'resources/lib/jquery/jquery.mockjax.js',
+ 'scripts' => 'resources/lib/jquery.mockjax.js',
],
'jquery.mw-jump' => [
'scripts' => 'resources/src/jquery/jquery.mw-jump.js',
],
'jquery.jStorage' => [
'deprecated' => 'Please use "mediawiki.storage" instead.',
- 'scripts' => 'resources/lib/jquery/jquery.jStorage.js',
+ 'scripts' => 'resources/lib/jquery.jStorage.js',
],
'jquery.suggestions' => [
'targets' => [ 'desktop', 'mobile' ],
'targets' => [ 'mobile', 'desktop' ],
],
'jquery.throttle-debounce' => [
- 'scripts' => 'resources/lib/jquery/jquery.ba-throttle-debounce.js',
+ 'scripts' => 'resources/lib/jquery.ba-throttle-debounce.js',
'targets' => [ 'desktop', 'mobile' ],
],
'jquery.xmldom' => [
- 'scripts' => 'resources/lib/jquery/jquery.xmldom.js',
+ 'scripts' => 'resources/lib/jquery.xmldom.js',
],
/* jQuery Tipsy */
'oojs-ui-widgets' => [
'class' => ResourceLoaderOOUIFileModule::class,
'scripts' => 'resources/lib/oojs-ui/oojs-ui-widgets.js',
+ 'themeStyles' => 'widgets',
'dependencies' => [
'oojs-ui-core',
- 'oojs-ui-widgets.styles',
'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-content',
'oojs-ui.styles.icons-editing-advanced',
--- /dev/null
+/*
+ * jQuery Asynchronous Plugin 1.0
+ *
+ * Copyright (c) 2008 Vincent Robert (genezys.net)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ */
+(function($){
+
+// opts.delay : (default 10) delay between async call in ms
+// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
+// opts.test : (default true) function to test in the while test part
+// opts.loop : (default empty) function to call in the while loop part
+// opts.end : (default empty) function to call at the end of the while loop
+$.whileAsync = function(opts) {
+ var delay = Math.abs(opts.delay) || 10,
+ bulk = isNaN(opts.bulk) ? 500 : Math.abs(opts.bulk),
+ test = opts.test || function(){ return true; },
+ loop = opts.loop || function(){},
+ end = opts.end || function(){};
+
+ (function(){
+
+ var t = false,
+ begin = new Date();
+
+ while( t = test() ) {
+ loop();
+ if( bulk === 0 || (new Date() - begin) > bulk ) {
+ break;
+ }
+ }
+ if( t ) {
+ setTimeout(arguments.callee, delay);
+ }
+ else {
+ end();
+ }
+
+ })();
+};
+
+// opts.delay : (default 10) delay between async call in ms
+// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
+// opts.loop : (default empty) function to call in the each loop part, signature: function(index, value) this = value
+// opts.end : (default empty) function to call at the end of the each loop
+$.eachAsync = function(array, opts) {
+ var i = 0,
+ l = array.length,
+ loop = opts.loop || function(){};
+
+ $.whileAsync(
+ $.extend(opts, {
+ test: function() { return i < l; },
+ loop: function() {
+ var val = array[i];
+ return loop.call(val, i++, val);
+ }
+ })
+ );
+};
+
+$.fn.eachAsync = function(opts) {
+ $.eachAsync(this, opts);
+ return this;
+}
+
+})(jQuery);
\ No newline at end of file
--- /dev/null
+/*!
+ * jQuery throttle / debounce - v1.1 - 3/7/2010
+ * http://benalman.com/projects/jquery-throttle-debounce-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+
+// Script: jQuery throttle / debounce: Sometimes, less is more!
+//
+// *Version: 1.1, Last updated: 3/7/2010*
+//
+// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/
+// GitHub - http://github.com/cowboy/jquery-throttle-debounce/
+// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js
+// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb)
+//
+// About: License
+//
+// Copyright (c) 2010 "Cowboy" Ben Alman,
+// Dual licensed under the MIT and GPL licenses.
+// http://benalman.com/about/license/
+//
+// About: Examples
+//
+// These working examples, complete with fully commented code, illustrate a few
+// ways in which this plugin can be used.
+//
+// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/
+// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
+//
+// About: Support and Testing
+//
+// Information about what version or versions of jQuery this plugin has been
+// tested with, what browsers it has been tested in, and where the unit tests
+// reside (so you can test it yourself).
+//
+// jQuery Versions - none, 1.3.2, 1.4.2
+// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1.
+// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/
+//
+// About: Release History
+//
+// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks
+// executed later than they should. Reworked a fair amount of internal
+// logic as well.
+// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over
+// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the
+// no_trailing throttle parameter and debounce functionality.
+//
+// Topic: Note for non-jQuery users
+//
+// jQuery isn't actually required for this plugin, because nothing internal
+// uses any jQuery methods or properties. jQuery is just used as a namespace
+// under which these methods can exist.
+//
+// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist
+// when this plugin is loaded, the method described below will be created in
+// the `Cowboy` namespace. Usage will be exactly the same, but instead of
+// $.method() or jQuery.method(), you'll need to use Cowboy.method().
+
+(function(window,undefined){
+ '$:nomunge'; // Used by YUI compressor.
+
+ // Since jQuery really isn't required for this plugin, use `jQuery` as the
+ // namespace only if it already exists, otherwise use the `Cowboy` namespace,
+ // creating it if necessary.
+ var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ),
+
+ // Internal method reference.
+ jq_throttle;
+
+ // Method: jQuery.throttle
+ //
+ // Throttle execution of a function. Especially useful for rate limiting
+ // execution of handlers on events like resize and scroll. If you want to
+ // rate-limit execution of a function to a single time, see the
+ // <jQuery.debounce> method.
+ //
+ // In this visualization, | is a throttled-function call and X is the actual
+ // callback execution:
+ //
+ // > Throttled with `no_trailing` specified as false or unspecified:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X X X X X X X X X X X
+ // >
+ // > Throttled with `no_trailing` specified as true:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X X X X X X X X X
+ //
+ // Usage:
+ //
+ // > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback );
+ // >
+ // > jQuery('selector').bind( 'someevent', throttled );
+ // > jQuery('selector').unbind( 'someevent', throttled );
+ //
+ // This also works in jQuery 1.4+:
+ //
+ // > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) );
+ // > jQuery('selector').unbind( 'someevent', callback );
+ //
+ // Arguments:
+ //
+ // delay - (Number) A zero-or-greater delay in milliseconds. For event
+ // callbacks, values around 100 or 250 (or even higher) are most useful.
+ // no_trailing - (Boolean) Optional, defaults to false. If no_trailing is
+ // true, callback will only execute every `delay` milliseconds while the
+ // throttled-function is being called. If no_trailing is false or
+ // unspecified, callback will be executed one final time after the last
+ // throttled-function call. (After the throttled-function has not been
+ // called for `delay` milliseconds, the internal counter is reset)
+ // callback - (Function) A function to be executed after delay milliseconds.
+ // The `this` context and all arguments are passed through, as-is, to
+ // `callback` when the throttled-function is executed.
+ //
+ // Returns:
+ //
+ // (Function) A new, throttled, function.
+
+ $.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) {
+ // After wrapper has stopped being called, this timeout ensures that
+ // `callback` is executed at the proper times in `throttle` and `end`
+ // debounce modes.
+ var timeout_id,
+
+ // Keep track of the last time `callback` was executed.
+ last_exec = 0;
+
+ // `no_trailing` defaults to falsy.
+ if ( typeof no_trailing !== 'boolean' ) {
+ debounce_mode = callback;
+ callback = no_trailing;
+ no_trailing = undefined;
+ }
+
+ // The `wrapper` function encapsulates all of the throttling / debouncing
+ // functionality and when executed will limit the rate at which `callback`
+ // is executed.
+ function wrapper() {
+ var that = this,
+ elapsed = +new Date() - last_exec,
+ args = arguments;
+
+ // Execute `callback` and update the `last_exec` timestamp.
+ function exec() {
+ last_exec = +new Date();
+ callback.apply( that, args );
+ };
+
+ // If `debounce_mode` is true (at_begin) this is used to clear the flag
+ // to allow future `callback` executions.
+ function clear() {
+ timeout_id = undefined;
+ };
+
+ if ( debounce_mode && !timeout_id ) {
+ // Since `wrapper` is being called for the first time and
+ // `debounce_mode` is true (at_begin), execute `callback`.
+ exec();
+ }
+
+ // Clear any existing timeout.
+ timeout_id && clearTimeout( timeout_id );
+
+ if ( debounce_mode === undefined && elapsed > delay ) {
+ // In throttle mode, if `delay` time has been exceeded, execute
+ // `callback`.
+ exec();
+
+ } else if ( no_trailing !== true ) {
+ // In trailing throttle mode, since `delay` time has not been
+ // exceeded, schedule `callback` to execute `delay` ms after most
+ // recent execution.
+ //
+ // If `debounce_mode` is true (at_begin), schedule `clear` to execute
+ // after `delay` ms.
+ //
+ // If `debounce_mode` is false (at end), schedule `callback` to
+ // execute after `delay` ms.
+ timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay );
+ }
+ };
+
+ // Set the guid of `wrapper` function to the same of original callback, so
+ // it can be removed in jQuery 1.4+ .unbind or .die by using the original
+ // callback as a reference.
+ if ( $.guid ) {
+ wrapper.guid = callback.guid = callback.guid || $.guid++;
+ }
+
+ // Return the wrapper function.
+ return wrapper;
+ };
+
+ // Method: jQuery.debounce
+ //
+ // Debounce execution of a function. Debouncing, unlike throttling,
+ // guarantees that a function is only executed a single time, either at the
+ // very beginning of a series of calls, or at the very end. If you want to
+ // simply rate-limit execution of a function, see the <jQuery.throttle>
+ // method.
+ //
+ // In this visualization, | is a debounced-function call and X is the actual
+ // callback execution:
+ //
+ // > Debounced with `at_begin` specified as false or unspecified:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X
+ // >
+ // > Debounced with `at_begin` specified as true:
+ // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
+ // > X X
+ //
+ // Usage:
+ //
+ // > var debounced = jQuery.debounce( delay, [ at_begin, ] callback );
+ // >
+ // > jQuery('selector').bind( 'someevent', debounced );
+ // > jQuery('selector').unbind( 'someevent', debounced );
+ //
+ // This also works in jQuery 1.4+:
+ //
+ // > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) );
+ // > jQuery('selector').unbind( 'someevent', callback );
+ //
+ // Arguments:
+ //
+ // delay - (Number) A zero-or-greater delay in milliseconds. For event
+ // callbacks, values around 100 or 250 (or even higher) are most useful.
+ // at_begin - (Boolean) Optional, defaults to false. If at_begin is false or
+ // unspecified, callback will only be executed `delay` milliseconds after
+ // the last debounced-function call. If at_begin is true, callback will be
+ // executed only at the first debounced-function call. (After the
+ // throttled-function has not been called for `delay` milliseconds, the
+ // internal counter is reset)
+ // callback - (Function) A function to be executed after delay milliseconds.
+ // The `this` context and all arguments are passed through, as-is, to
+ // `callback` when the debounced-function is executed.
+ //
+ // Returns:
+ //
+ // (Function) A new, debounced, function.
+
+ $.debounce = function( delay, at_begin, callback ) {
+ return callback === undefined
+ ? jq_throttle( delay, at_begin, false )
+ : jq_throttle( delay, callback, at_begin !== false );
+ };
+
+})(this);
--- /dev/null
+/*!
+ * jQuery Cookie Plugin v1.3.1
+ * https://github.com/carhartl/jquery-cookie
+ *
+ * Copyright 2013 Klaus Hartl
+ * Released under the MIT license
+ */
+(function ($, document, undefined) {
+
+ var pluses = /\+/g;
+
+ function raw(s) {
+ return s;
+ }
+
+ function decoded(s) {
+ return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
+ }
+
+ function unRfc2068(value) {
+ if (value.indexOf('"') === 0) {
+ // This is a quoted cookie as according to RFC2068, unescape
+ value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
+ }
+ return value;
+ }
+
+ function fromJSON(value) {
+ return config.json ? JSON.parse(value) : value;
+ }
+
+ var config = $.cookie = function (key, value, options) {
+
+ // write
+ if (value !== undefined) {
+ options = $.extend({}, config.defaults, options);
+
+ if (value === null) {
+ options.expires = -1;
+ }
+
+ if (typeof options.expires === 'number') {
+ var days = options.expires, t = options.expires = new Date();
+ t.setDate(t.getDate() + days);
+ }
+
+ value = config.json ? JSON.stringify(value) : String(value);
+
+ return (document.cookie = [
+ encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+ options.path ? '; path=' + options.path : '',
+ options.domain ? '; domain=' + options.domain : '',
+ options.secure ? '; secure' : ''
+ ].join(''));
+ }
+
+ // read
+ var decode = config.raw ? raw : decoded;
+ var cookies = document.cookie.split('; ');
+ var result = key ? null : {};
+ for (var i = 0, l = cookies.length; i < l; i++) {
+ var parts = cookies[i].split('=');
+ var name = decode(parts.shift());
+ var cookie = decode(parts.join('='));
+
+ if (key && key === name) {
+ result = fromJSON(cookie);
+ break;
+ }
+
+ if (!key) {
+ result[name] = fromJSON(cookie);
+ }
+ }
+
+ return result;
+ };
+
+ config.defaults = {};
+
+ $.removeCookie = function (key, options) {
+ if ($.cookie(key) !== null) {
+ $.cookie(key, null, options);
+ return true;
+ }
+ return false;
+ };
+
+})(jQuery, document);
--- /dev/null
+/*!
+ * jQuery Form Plugin
+ * version: 3.14 (30-JUL-2012)
+ * @requires jQuery v1.3.2 or later
+ *
+ * Examples and documentation at: http://malsup.com/jquery/form/
+ * Project repository: https://github.com/malsup/form
+ * Dual licensed under the MIT and GPL licenses:
+ * http://malsup.github.com/mit-license.txt
+ * http://malsup.github.com/gpl-license-v2.txt
+ */
+/*global ActiveXObject alert */
+;(function($) {
+"use strict";
+
+/*
+ Usage Note:
+ -----------
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
+ functions are mutually exclusive. Use ajaxSubmit if you want
+ to bind your own submit handler to the form. For example,
+
+ $(document).ready(function() {
+ $('#myForm').on('submit', function(e) {
+ e.preventDefault(); // <-- important
+ $(this).ajaxSubmit({
+ target: '#output'
+ });
+ });
+ });
+
+ Use ajaxForm when you want the plugin to manage all the event binding
+ for you. For example,
+
+ $(document).ready(function() {
+ $('#myForm').ajaxForm({
+ target: '#output'
+ });
+ });
+
+ You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
+ form does not have to exist when you invoke ajaxForm:
+
+ $('#myForm').ajaxForm({
+ delegation: true,
+ target: '#output'
+ });
+
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
+ at the appropriate time.
+*/
+
+/**
+ * Feature detection
+ */
+var feature = {};
+feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
+feature.formdata = window.FormData !== undefined;
+
+/**
+ * ajaxSubmit() provides a mechanism for immediately submitting
+ * an HTML form using AJAX.
+ */
+$.fn.ajaxSubmit = function(options) {
+ /*jshint scripturl:true */
+
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
+ if (!this.length) {
+ log('ajaxSubmit: skipping submit process - no element selected');
+ return this;
+ }
+
+ var method, action, url, $form = this;
+
+ if (typeof options == 'function') {
+ options = { success: options };
+ }
+
+ method = this.attr('method');
+ action = this.attr('action');
+ url = (typeof action === 'string') ? $.trim(action) : '';
+ url = url || window.location.href || '';
+ if (url) {
+ // clean url (don't include hash vaue)
+ url = (url.match(/^([^#]+)/)||[])[1];
+ }
+
+ options = $.extend(true, {
+ url: url,
+ success: $.ajaxSettings.success,
+ type: method || 'GET',
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
+ }, options);
+
+ // hook for manipulating the form data before it is extracted;
+ // convenient for use with rich editors like tinyMCE or FCKEditor
+ var veto = {};
+ this.trigger('form-pre-serialize', [this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
+ return this;
+ }
+
+ // provide opportunity to alter form data before it is serialized
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
+ return this;
+ }
+
+ var traditional = options.traditional;
+ if ( traditional === undefined ) {
+ traditional = $.ajaxSettings.traditional;
+ }
+
+ var elements = [];
+ var qx, a = this.formToArray(options.semantic, elements);
+ if (options.data) {
+ options.extraData = options.data;
+ qx = $.param(options.data, traditional);
+ }
+
+ // give pre-submit callback an opportunity to abort the submit
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
+ return this;
+ }
+
+ // fire vetoable 'validate' event
+ this.trigger('form-submit-validate', [a, this, options, veto]);
+ if (veto.veto) {
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
+ return this;
+ }
+
+ var q = $.param(a, traditional);
+ if (qx) {
+ q = ( q ? (q + '&' + qx) : qx );
+ }
+ if (options.type.toUpperCase() == 'GET') {
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
+ options.data = null; // data is null for 'get'
+ }
+ else {
+ options.data = q; // data is the query string for 'post'
+ }
+
+ var callbacks = [];
+ if (options.resetForm) {
+ callbacks.push(function() { $form.resetForm(); });
+ }
+ if (options.clearForm) {
+ callbacks.push(function() { $form.clearForm(options.includeHidden); });
+ }
+
+ // perform a load on the target only if dataType is not provided
+ if (!options.dataType && options.target) {
+ var oldSuccess = options.success || function(){};
+ callbacks.push(function(data) {
+ var fn = options.replaceTarget ? 'replaceWith' : 'html';
+ $(options.target)[fn](data).each(oldSuccess, arguments);
+ });
+ }
+ else if (options.success) {
+ callbacks.push(options.success);
+ }
+
+ options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
+ var context = options.context || this ; // jQuery 1.4+ supports scope context
+ for (var i=0, max=callbacks.length; i < max; i++) {
+ callbacks[i].apply(context, [data, status, xhr || $form, $form]);
+ }
+ };
+
+ // are there files to upload?
+ var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
+ var hasFileInputs = fileInputs.length > 0;
+ var mp = 'multipart/form-data';
+ var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
+
+ var fileAPI = feature.fileapi && feature.formdata;
+ log("fileAPI :" + fileAPI);
+ var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
+
+ // options.iframe allows user to force iframe mode
+ // 06-NOV-09: now defaulting to iframe mode if file input is detected
+ if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
+ if (options.closeKeepAlive) {
+ $.get(options.closeKeepAlive, function() {
+ fileUploadIframe(a);
+ });
+ }
+ else {
+ fileUploadIframe(a);
+ }
+ }
+ else if ((hasFileInputs || multipart) && fileAPI) {
+ fileUploadXhr(a);
+ }
+ else {
+ $.ajax(options);
+ }
+
+ // clear element array
+ for (var k=0; k < elements.length; k++)
+ elements[k] = null;
+
+ // fire 'notify' event
+ this.trigger('form-submit-notify', [this, options]);
+ return this;
+
+ // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
+ function fileUploadXhr(a) {
+ var formdata = new FormData();
+
+ for (var i=0; i < a.length; i++) {
+ formdata.append(a[i].name, a[i].value);
+ }
+
+ if (options.extraData) {
+ for (var p in options.extraData)
+ if (options.extraData.hasOwnProperty(p))
+ formdata.append(p, options.extraData[p]);
+ }
+
+ options.data = null;
+
+ var s = $.extend(true, {}, $.ajaxSettings, options, {
+ contentType: false,
+ processData: false,
+ cache: false,
+ type: 'POST'
+ });
+
+ if (options.uploadProgress) {
+ // workaround because jqXHR does not expose upload property
+ s.xhr = function() {
+ var xhr = jQuery.ajaxSettings.xhr();
+ if (xhr.upload) {
+ xhr.upload.onprogress = function(event) {
+ var percent = 0;
+ var position = event.loaded || event.position; /*event.position is deprecated*/
+ var total = event.total;
+ if (event.lengthComputable) {
+ percent = Math.ceil(position / total * 100);
+ }
+ options.uploadProgress(event, position, total, percent);
+ };
+ }
+ return xhr;
+ };
+ }
+
+ s.data = null;
+ var beforeSend = s.beforeSend;
+ s.beforeSend = function(xhr, o) {
+ o.data = formdata;
+ if(beforeSend)
+ beforeSend.call(this, xhr, o);
+ };
+ $.ajax(s);
+ }
+
+ // private function for handling file uploads (hat tip to YAHOO!)
+ function fileUploadIframe(a) {
+ var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
+ var useProp = !!$.fn.prop;
+
+ if ($(':input[name=submit],:input[id=submit]', form).length) {
+ // if there is an input with a name or id of 'submit' then we won't be
+ // able to invoke the submit fn on the form (at least not x-browser)
+ alert('Error: Form elements must not have name or id of "submit".');
+ return;
+ }
+
+ if (a) {
+ // ensure that every serialized input is still enabled
+ for (i=0; i < elements.length; i++) {
+ el = $(elements[i]);
+ if ( useProp )
+ el.prop('disabled', false);
+ else
+ el.removeAttr('disabled');
+ }
+ }
+
+ s = $.extend(true, {}, $.ajaxSettings, options);
+ s.context = s.context || s;
+ id = 'jqFormIO' + (new Date().getTime());
+ if (s.iframeTarget) {
+ $io = $(s.iframeTarget);
+ n = $io.attr('name');
+ if (!n)
+ $io.attr('name', id);
+ else
+ id = n;
+ }
+ else {
+ $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
+ }
+ io = $io[0];
+
+
+ xhr = { // mock object
+ aborted: 0,
+ responseText: null,
+ responseXML: null,
+ status: 0,
+ statusText: 'n/a',
+ getAllResponseHeaders: function() {},
+ getResponseHeader: function() {},
+ setRequestHeader: function() {},
+ abort: function(status) {
+ var e = (status === 'timeout' ? 'timeout' : 'aborted');
+ log('aborting upload... ' + e);
+ this.aborted = 1;
+ // #214
+ if (io.contentWindow.document.execCommand) {
+ try { // #214
+ io.contentWindow.document.execCommand('Stop');
+ } catch(ignore) {}
+ }
+ $io.attr('src', s.iframeSrc); // abort op in progress
+ xhr.error = e;
+ if (s.error)
+ s.error.call(s.context, xhr, e, status);
+ if (g)
+ $.event.trigger("ajaxError", [xhr, s, e]);
+ if (s.complete)
+ s.complete.call(s.context, xhr, e);
+ }
+ };
+
+ g = s.global;
+ // trigger ajax global events so that activity/block indicators work like normal
+ if (g && 0 === $.active++) {
+ $.event.trigger("ajaxStart");
+ }
+ if (g) {
+ $.event.trigger("ajaxSend", [xhr, s]);
+ }
+
+ if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
+ if (s.global) {
+ $.active--;
+ }
+ return;
+ }
+ if (xhr.aborted) {
+ return;
+ }
+
+ // add submitting element to data if we know it
+ sub = form.clk;
+ if (sub) {
+ n = sub.name;
+ if (n && !sub.disabled) {
+ s.extraData = s.extraData || {};
+ s.extraData[n] = sub.value;
+ if (sub.type == "image") {
+ s.extraData[n+'.x'] = form.clk_x;
+ s.extraData[n+'.y'] = form.clk_y;
+ }
+ }
+ }
+
+ var CLIENT_TIMEOUT_ABORT = 1;
+ var SERVER_ABORT = 2;
+
+ function getDoc(frame) {
+ var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
+ return doc;
+ }
+
+ // Rails CSRF hack (thanks to Yvan Barthelemy)
+ var csrf_token = $('meta[name=csrf-token]').attr('content');
+ var csrf_param = $('meta[name=csrf-param]').attr('content');
+ if (csrf_param && csrf_token) {
+ s.extraData = s.extraData || {};
+ s.extraData[csrf_param] = csrf_token;
+ }
+
+ // take a breath so that pending repaints get some cpu time before the upload starts
+ function doSubmit() {
+ // make sure form attrs are set
+ var t = $form.attr('target'), a = $form.attr('action');
+
+ // update form attrs in IE friendly way
+ form.setAttribute('target',id);
+ if (!method) {
+ form.setAttribute('method', 'POST');
+ }
+ if (a != s.url) {
+ form.setAttribute('action', s.url);
+ }
+
+ // ie borks in some cases when setting encoding
+ if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
+ $form.attr({
+ encoding: 'multipart/form-data',
+ enctype: 'multipart/form-data'
+ });
+ }
+
+ // support timout
+ if (s.timeout) {
+ timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
+ }
+
+ // look for server aborts
+ function checkState() {
+ try {
+ var state = getDoc(io).readyState;
+ log('state = ' + state);
+ if (state && state.toLowerCase() == 'uninitialized')
+ setTimeout(checkState,50);
+ }
+ catch(e) {
+ log('Server abort: ' , e, ' (', e.name, ')');
+ cb(SERVER_ABORT);
+ if (timeoutHandle)
+ clearTimeout(timeoutHandle);
+ timeoutHandle = undefined;
+ }
+ }
+
+ // add "extra" data to form if provided in options
+ var extraInputs = [];
+ try {
+ if (s.extraData) {
+ for (var n in s.extraData) {
+ if (s.extraData.hasOwnProperty(n)) {
+ // if using the $.param format that allows for multiple values with the same name
+ if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
+ extraInputs.push(
+ $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
+ .appendTo(form)[0]);
+ } else {
+ extraInputs.push(
+ $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
+ .appendTo(form)[0]);
+ }
+ }
+ }
+ }
+
+ if (!s.iframeTarget) {
+ // add iframe to doc and submit the form
+ $io.appendTo('body');
+ if (io.attachEvent)
+ io.attachEvent('onload', cb);
+ else
+ io.addEventListener('load', cb, false);
+ }
+ setTimeout(checkState,15);
+ form.submit();
+ }
+ finally {
+ // reset attrs and remove "extra" input elements
+ form.setAttribute('action',a);
+ if(t) {
+ form.setAttribute('target', t);
+ } else {
+ $form.removeAttr('target');
+ }
+ $(extraInputs).remove();
+ }
+ }
+
+ if (s.forceSync) {
+ doSubmit();
+ }
+ else {
+ setTimeout(doSubmit, 10); // this lets dom updates render
+ }
+
+ var data, doc, domCheckCount = 50, callbackProcessed;
+
+ function cb(e) {
+ if (xhr.aborted || callbackProcessed) {
+ return;
+ }
+ try {
+ doc = getDoc(io);
+ }
+ catch(ex) {
+ log('cannot access response document: ', ex);
+ e = SERVER_ABORT;
+ }
+ if (e === CLIENT_TIMEOUT_ABORT && xhr) {
+ xhr.abort('timeout');
+ return;
+ }
+ else if (e == SERVER_ABORT && xhr) {
+ xhr.abort('server abort');
+ return;
+ }
+
+ if (!doc || doc.location.href == s.iframeSrc) {
+ // response not received yet
+ if (!timedOut)
+ return;
+ }
+ if (io.detachEvent)
+ io.detachEvent('onload', cb);
+ else
+ io.removeEventListener('load', cb, false);
+
+ var status = 'success', errMsg;
+ try {
+ if (timedOut) {
+ throw 'timeout';
+ }
+
+ var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
+ log('isXml='+isXml);
+ if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
+ if (--domCheckCount) {
+ // in some browsers (Opera) the iframe DOM is not always traversable when
+ // the onload callback fires, so we loop a bit to accommodate
+ log('requeing onLoad callback, DOM not available');
+ setTimeout(cb, 250);
+ return;
+ }
+ // let this fall through because server response could be an empty document
+ //log('Could not access iframe DOM after mutiple tries.');
+ //throw 'DOMException: not available';
+ }
+
+ //log('response detected');
+ var docRoot = doc.body ? doc.body : doc.documentElement;
+ xhr.responseText = docRoot ? docRoot.innerHTML : null;
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
+ if (isXml)
+ s.dataType = 'xml';
+ xhr.getResponseHeader = function(header){
+ var headers = {'content-type': s.dataType};
+ return headers[header];
+ };
+ // support for XHR 'status' & 'statusText' emulation :
+ if (docRoot) {
+ xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
+ xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
+ }
+
+ var dt = (s.dataType || '').toLowerCase();
+ var scr = /(json|script|text)/.test(dt);
+ if (scr || s.textarea) {
+ // see if user embedded response in textarea
+ var ta = doc.getElementsByTagName('textarea')[0];
+ if (ta) {
+ xhr.responseText = ta.value;
+ // support for XHR 'status' & 'statusText' emulation :
+ xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
+ xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
+ }
+ else if (scr) {
+ // account for browsers injecting pre around json response
+ var pre = doc.getElementsByTagName('pre')[0];
+ var b = doc.getElementsByTagName('body')[0];
+ if (pre) {
+ xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
+ }
+ else if (b) {
+ xhr.responseText = b.textContent ? b.textContent : b.innerText;
+ }
+ }
+ }
+ else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
+ xhr.responseXML = toXml(xhr.responseText);
+ }
+
+ try {
+ data = httpData(xhr, dt, s);
+ }
+ catch (e) {
+ status = 'parsererror';
+ xhr.error = errMsg = (e || status);
+ }
+ }
+ catch (e) {
+ log('error caught: ',e);
+ status = 'error';
+ xhr.error = errMsg = (e || status);
+ }
+
+ if (xhr.aborted) {
+ log('upload aborted');
+ status = null;
+ }
+
+ if (xhr.status) { // we've set xhr.status
+ status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
+ }
+
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
+ if (status === 'success') {
+ if (s.success)
+ s.success.call(s.context, data, 'success', xhr);
+ if (g)
+ $.event.trigger("ajaxSuccess", [xhr, s]);
+ }
+ else if (status) {
+ if (errMsg === undefined)
+ errMsg = xhr.statusText;
+ if (s.error)
+ s.error.call(s.context, xhr, status, errMsg);
+ if (g)
+ $.event.trigger("ajaxError", [xhr, s, errMsg]);
+ }
+
+ if (g)
+ $.event.trigger("ajaxComplete", [xhr, s]);
+
+ if (g && ! --$.active) {
+ $.event.trigger("ajaxStop");
+ }
+
+ if (s.complete)
+ s.complete.call(s.context, xhr, status);
+
+ callbackProcessed = true;
+ if (s.timeout)
+ clearTimeout(timeoutHandle);
+
+ // clean up
+ setTimeout(function() {
+ if (!s.iframeTarget)
+ $io.remove();
+ xhr.responseXML = null;
+ }, 100);
+ }
+
+ var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML(s);
+ }
+ else {
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
+ }
+ return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
+ };
+ var parseJSON = $.parseJSON || function(s) {
+ /*jslint evil:true */
+ return window['eval']('(' + s + ')');
+ };
+
+ var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
+
+ var ct = xhr.getResponseHeader('content-type') || '',
+ xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
+ data = xml ? xhr.responseXML : xhr.responseText;
+
+ if (xml && data.documentElement.nodeName === 'parsererror') {
+ if ($.error)
+ $.error('parsererror');
+ }
+ if (s && s.dataFilter) {
+ data = s.dataFilter(data, type);
+ }
+ if (typeof data === 'string') {
+ if (type === 'json' || !type && ct.indexOf('json') >= 0) {
+ data = parseJSON(data);
+ } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
+ $.globalEval(data);
+ }
+ }
+ return data;
+ };
+ }
+};
+
+/**
+ * ajaxForm() provides a mechanism for fully automating form submission.
+ *
+ * The advantages of using this method instead of ajaxSubmit() are:
+ *
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
+ * is used to submit the form).
+ * 2. This method will include the submit element's name/value data (for the element that was
+ * used to submit the form).
+ * 3. This method binds the submit() method to the form for you.
+ *
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
+ * passes the options argument along after properly binding events for submit elements and
+ * the form itself.
+ */
+$.fn.ajaxForm = function(options) {
+ options = options || {};
+ options.delegation = options.delegation && $.isFunction($.fn.on);
+
+ // in jQuery 1.3+ we can fix mistakes with the ready state
+ if (!options.delegation && this.length === 0) {
+ var o = { s: this.selector, c: this.context };
+ if (!$.isReady && o.s) {
+ log('DOM not ready, queuing ajaxForm');
+ $(function() {
+ $(o.s,o.c).ajaxForm(options);
+ });
+ return this;
+ }
+ // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
+ log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
+ return this;
+ }
+
+ if ( options.delegation ) {
+ $(document)
+ .off('submit.form-plugin', this.selector, doAjaxSubmit)
+ .off('click.form-plugin', this.selector, captureSubmittingElement)
+ .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
+ .on('click.form-plugin', this.selector, options, captureSubmittingElement);
+ return this;
+ }
+
+ return this.ajaxFormUnbind()
+ .bind('submit.form-plugin', options, doAjaxSubmit)
+ .bind('click.form-plugin', options, captureSubmittingElement);
+};
+
+// private event handlers
+function doAjaxSubmit(e) {
+ /*jshint validthis:true */
+ var options = e.data;
+ if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
+ e.preventDefault();
+ $(this).ajaxSubmit(options);
+ }
+}
+
+function captureSubmittingElement(e) {
+ /*jshint validthis:true */
+ var target = e.target;
+ var $el = $(target);
+ if (!($el.is(":submit,input:image"))) {
+ // is this a child element of the submit el? (ex: a span within a button)
+ var t = $el.closest(':submit');
+ if (t.length === 0) {
+ return;
+ }
+ target = t[0];
+ }
+ var form = this;
+ form.clk = target;
+ if (target.type == 'image') {
+ if (e.offsetX !== undefined) {
+ form.clk_x = e.offsetX;
+ form.clk_y = e.offsetY;
+ } else if (typeof $.fn.offset == 'function') {
+ var offset = $el.offset();
+ form.clk_x = e.pageX - offset.left;
+ form.clk_y = e.pageY - offset.top;
+ } else {
+ form.clk_x = e.pageX - target.offsetLeft;
+ form.clk_y = e.pageY - target.offsetTop;
+ }
+ }
+ // clear form vars
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
+}
+
+
+// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
+$.fn.ajaxFormUnbind = function() {
+ return this.unbind('submit.form-plugin click.form-plugin');
+};
+
+/**
+ * formToArray() gathers form element data into an array of objects that can
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
+ * Each object in the array has both a 'name' and 'value' property. An example of
+ * an array for a simple login form might be:
+ *
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
+ *
+ * It is this array that is passed to pre-submit callback functions provided to the
+ * ajaxSubmit() and ajaxForm() methods.
+ */
+$.fn.formToArray = function(semantic, elements) {
+ var a = [];
+ if (this.length === 0) {
+ return a;
+ }
+
+ var form = this[0];
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
+ if (!els) {
+ return a;
+ }
+
+ var i,j,n,v,el,max,jmax;
+ for(i=0, max=els.length; i < max; i++) {
+ el = els[i];
+ n = el.name;
+ if (!n) {
+ continue;
+ }
+
+ if (semantic && form.clk && el.type == "image") {
+ // handle image inputs on the fly when semantic == true
+ if(!el.disabled && form.clk == el) {
+ a.push({name: n, value: $(el).val(), type: el.type });
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ continue;
+ }
+
+ v = $.fieldValue(el, true);
+ if (v && v.constructor == Array) {
+ if (elements)
+ elements.push(el);
+ for(j=0, jmax=v.length; j < jmax; j++) {
+ a.push({name: n, value: v[j]});
+ }
+ }
+ else if (feature.fileapi && el.type == 'file' && !el.disabled) {
+ if (elements)
+ elements.push(el);
+ var files = el.files;
+ if (files.length) {
+ for (j=0; j < files.length; j++) {
+ a.push({name: n, value: files[j], type: el.type});
+ }
+ }
+ else {
+ // #180
+ a.push({ name: n, value: '', type: el.type });
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ if (elements)
+ elements.push(el);
+ a.push({name: n, value: v, type: el.type, required: el.required});
+ }
+ }
+
+ if (!semantic && form.clk) {
+ // input type=='image' are not found in elements array! handle it here
+ var $input = $(form.clk), input = $input[0];
+ n = input.name;
+ if (n && !input.disabled && input.type == 'image') {
+ a.push({name: n, value: $input.val()});
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
+ }
+ }
+ return a;
+};
+
+/**
+ * Serializes form data into a 'submittable' string. This method will return a string
+ * in the format: name1=value1&name2=value2
+ */
+$.fn.formSerialize = function(semantic) {
+ //hand off to jQuery.param for proper encoding
+ return $.param(this.formToArray(semantic));
+};
+
+/**
+ * Serializes all field elements in the jQuery object into a query string.
+ * This method will return a string in the format: name1=value1&name2=value2
+ */
+$.fn.fieldSerialize = function(successful) {
+ var a = [];
+ this.each(function() {
+ var n = this.name;
+ if (!n) {
+ return;
+ }
+ var v = $.fieldValue(this, successful);
+ if (v && v.constructor == Array) {
+ for (var i=0,max=v.length; i < max; i++) {
+ a.push({name: n, value: v[i]});
+ }
+ }
+ else if (v !== null && typeof v != 'undefined') {
+ a.push({name: this.name, value: v});
+ }
+ });
+ //hand off to jQuery.param for proper encoding
+ return $.param(a);
+};
+
+/**
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
+ *
+ * <form><fieldset>
+ * <input name="A" type="text" />
+ * <input name="A" type="text" />
+ * <input name="B" type="checkbox" value="B1" />
+ * <input name="B" type="checkbox" value="B2"/>
+ * <input name="C" type="radio" value="C1" />
+ * <input name="C" type="radio" value="C2" />
+ * </fieldset></form>
+ *
+ * var v = $(':text').fieldValue();
+ * // if no values are entered into the text inputs
+ * v == ['','']
+ * // if values entered into the text inputs are 'foo' and 'bar'
+ * v == ['foo','bar']
+ *
+ * var v = $(':checkbox').fieldValue();
+ * // if neither checkbox is checked
+ * v === undefined
+ * // if both checkboxes are checked
+ * v == ['B1', 'B2']
+ *
+ * var v = $(':radio').fieldValue();
+ * // if neither radio is checked
+ * v === undefined
+ * // if first radio is checked
+ * v == ['C1']
+ *
+ * The successful argument controls whether or not the field element must be 'successful'
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
+ * The default value of the successful argument is true. If this value is false the value(s)
+ * for each element is returned.
+ *
+ * Note: This method *always* returns an array. If no valid value can be determined the
+ * array will be empty, otherwise it will contain one or more values.
+ */
+$.fn.fieldValue = function(successful) {
+ for (var val=[], i=0, max=this.length; i < max; i++) {
+ var el = this[i];
+ var v = $.fieldValue(el, successful);
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
+ continue;
+ }
+ if (v.constructor == Array)
+ $.merge(val, v);
+ else
+ val.push(v);
+ }
+ return val;
+};
+
+/**
+ * Returns the value of the field element.
+ */
+$.fieldValue = function(el, successful) {
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
+ if (successful === undefined) {
+ successful = true;
+ }
+
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
+ tag == 'select' && el.selectedIndex == -1)) {
+ return null;
+ }
+
+ if (tag == 'select') {
+ var index = el.selectedIndex;
+ if (index < 0) {
+ return null;
+ }
+ var a = [], ops = el.options;
+ var one = (t == 'select-one');
+ var max = (one ? index+1 : ops.length);
+ for(var i=(one ? index : 0); i < max; i++) {
+ var op = ops[i];
+ if (op.selected) {
+ var v = op.value;
+ if (!v) { // extra pain for IE...
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
+ }
+ if (one) {
+ return v;
+ }
+ a.push(v);
+ }
+ }
+ return a;
+ }
+ return $(el).val();
+};
+
+/**
+ * Clears the form data. Takes the following actions on the form's input fields:
+ * - input text fields will have their 'value' property set to the empty string
+ * - select elements will have their 'selectedIndex' property set to -1
+ * - checkbox and radio inputs will have their 'checked' property set to false
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
+ * - button elements will *not* be effected
+ */
+$.fn.clearForm = function(includeHidden) {
+ return this.each(function() {
+ $('input,select,textarea', this).clearFields(includeHidden);
+ });
+};
+
+/**
+ * Clears the selected form elements.
+ */
+$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
+ var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
+ return this.each(function() {
+ var t = this.type, tag = this.tagName.toLowerCase();
+ if (re.test(t) || tag == 'textarea') {
+ this.value = '';
+ }
+ else if (t == 'checkbox' || t == 'radio') {
+ this.checked = false;
+ }
+ else if (tag == 'select') {
+ this.selectedIndex = -1;
+ }
+ else if (includeHidden) {
+ // includeHidden can be the value true, or it can be a selector string
+ // indicating a special test; for example:
+ // $('#myForm').clearForm('.special:hidden')
+ // the above would clean hidden inputs that have the class of 'special'
+ if ( (includeHidden === true && /hidden/.test(t)) ||
+ (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
+ this.value = '';
+ }
+ });
+};
+
+/**
+ * Resets the form data. Causes all form elements to be reset to their original value.
+ */
+$.fn.resetForm = function() {
+ return this.each(function() {
+ // guard against an input with the name of 'reset'
+ // note that IE reports the reset function as an 'object'
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
+ this.reset();
+ }
+ });
+};
+
+/**
+ * Enables or disables any matching elements.
+ */
+$.fn.enable = function(b) {
+ if (b === undefined) {
+ b = true;
+ }
+ return this.each(function() {
+ this.disabled = !b;
+ });
+};
+
+/**
+ * Checks/unchecks any matching checkboxes or radio buttons and
+ * selects/deselects and matching option elements.
+ */
+$.fn.selected = function(select) {
+ if (select === undefined) {
+ select = true;
+ }
+ return this.each(function() {
+ var t = this.type;
+ if (t == 'checkbox' || t == 'radio') {
+ this.checked = select;
+ }
+ else if (this.tagName.toLowerCase() == 'option') {
+ var $sel = $(this).parent('select');
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
+ // deselect all other options
+ $sel.find('option').selected(false);
+ }
+ this.selected = select;
+ }
+ });
+};
+
+// expose debug var
+$.fn.ajaxSubmit.debug = false;
+
+// helper fn for console logging
+function log() {
+ if (!$.fn.ajaxSubmit.debug)
+ return;
+ var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
+ if (window.console && window.console.log) {
+ window.console.log(msg);
+ }
+ else if (window.opera && window.opera.postError) {
+ window.opera.postError(msg);
+ }
+}
+
+})(jQuery);
--- /dev/null
+/**
+ * jQuery fullscreen plugin v2.0.0-git (9f8f97d127)
+ * https://github.com/theopolisme/jquery-fullscreen
+ *
+ * Copyright (c) 2013 Theopolisme <theopolismewiki@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+( function ( $ ) {
+ var setupFullscreen,
+ fsClass = 'jq-fullscreened';
+
+ /**
+ * On fullscreenchange, trigger a jq-fullscreen-change event
+ * The event is given an object, which contains the fullscreened DOM element (element), if any
+ * and a boolean value (fullscreen) indicating if we've entered or exited fullscreen mode
+ * Also remove the 'fullscreened' class from elements that are no longer fullscreen
+ */
+ function handleFullscreenChange () {
+ var fullscreenElement = document.fullscreenElement ||
+ document.mozFullScreenElement ||
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement;
+
+ if ( !fullscreenElement ) {
+ $( '.' + fsClass ).data( 'isFullscreened', false ).removeClass( fsClass );
+ }
+
+ $( document ).trigger( $.Event( 'jq-fullscreen-change', { element: fullscreenElement, fullscreen: !!fullscreenElement } ) );
+ }
+
+ /**
+ * Enters full screen with the "this" element in focus.
+ * Check the .data( 'isFullscreened' ) of the return value to check
+ * success or failure, if you're into that sort of thing.
+ * @chainable
+ * @return {jQuery}
+ */
+ function enterFullscreen () {
+ var element = this.get(0),
+ $element = this.first();
+ if ( element ) {
+ if ( element.requestFullscreen ) {
+ element.requestFullscreen();
+ } else if ( element.mozRequestFullScreen ) {
+ element.mozRequestFullScreen();
+ } else if ( element.webkitRequestFullscreen ) {
+ element.webkitRequestFullscreen();
+ } else if ( element.msRequestFullscreen ) {
+ element.msRequestFullscreen();
+ } else {
+ // Unable to make fullscreen
+ $element.data( 'isFullscreened', false );
+ return this;
+ }
+ // Add the fullscreen class and data attribute to `element`
+ $element.addClass( fsClass ).data( 'isFullscreened', true );
+ return this;
+ } else {
+ $element.data( 'isFullscreened', false );
+ return this;
+ }
+ }
+
+ /**
+ * Brings the "this" element out of fullscreen.
+ * Check the .data( 'isFullscreened' ) of the return value to check
+ * success or failure, if you're into that sort of thing.
+ * @chainable
+ * @return {jQuery}
+ */
+ function exitFullscreen () {
+ var fullscreenElement = ( document.fullscreenElement ||
+ document.mozFullScreenElement ||
+ document.webkitFullscreenElement ||
+ document.msFullscreenElement );
+
+ // Ensure that we only exit fullscreen if exitFullscreen() is being called on the same element that is currently fullscreen
+ if ( fullscreenElement && this.get(0) === fullscreenElement ) {
+ if ( document.exitFullscreen ) {
+ document.exitFullscreen();
+ } else if ( document.mozCancelFullScreen ) {
+ document.mozCancelFullScreen();
+ } else if ( document.webkitCancelFullScreen ) {
+ document.webkitCancelFullScreen();
+ } else if ( document.msExitFullscreen ) {
+ document.msExitFullscreen();
+ } else {
+ // Unable to cancel fullscreen mode
+ return this;
+ }
+ // We don't need to remove the fullscreen class here,
+ // because it will be removed in handleFullscreenChange.
+ // But we should change the data on the element so the
+ // caller can check for success.
+ this.first().data( 'isFullscreened', false );
+ }
+
+ return this;
+ }
+
+ /**
+ * Set up fullscreen handling and install necessary event handlers.
+ * Return false if fullscreen is not supported.
+ */
+ setupFullscreen = function () {
+ if ( $.support.fullscreen ) {
+ // When the fullscreen mode is changed, trigger the
+ // fullscreen events (and when exiting,
+ // also remove the fullscreen class)
+ $( document ).on( 'fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange', handleFullscreenChange);
+ // Convenience wrapper so that one only needs to listen for
+ // 'fullscreenerror', not all of the prefixed versions
+ $( document ).on( 'webkitfullscreenerror mozfullscreenerror MSFullscreenError', function () {
+ $( document ).trigger( $.Event( 'fullscreenerror' ) );
+ } );
+ // Fullscreen has been set up, so always return true
+ setupFullscreen = function () { return true; };
+ return true;
+ } else {
+ // Always return false from now on, since fullscreen is not supported
+ setupFullscreen = function () { return false; };
+ return false;
+ }
+ };
+
+ /**
+ * Set up fullscreen handling if necessary, then make the first element
+ * matching the given selector fullscreen
+ * @chainable
+ * @return {jQuery}
+ */
+ $.fn.enterFullscreen = function () {
+ if ( setupFullscreen() ) {
+ $.fn.enterFullscreen = enterFullscreen;
+ return this.enterFullscreen();
+ } else {
+ $.fn.enterFullscreen = function () { return this; };
+ return this;
+ }
+ };
+
+ /**
+ * Set up fullscreen handling if necessary, then cancel fullscreen mode
+ * for the first element matching the given selector.
+ * @chainable
+ * @return {jQuery}
+ */
+ $.fn.exitFullscreen = function () {
+ if ( setupFullscreen() ) {
+ $.fn.exitFullscreen = exitFullscreen;
+ return this.exitFullscreen();
+ } else {
+ $.fn.exitFullscreen = function () { return this; };
+ return this;
+ }
+ };
+
+ $.support.fullscreen = document.fullscreenEnabled ||
+ document.webkitFullscreenEnabled ||
+ document.mozFullScreenEnabled ||
+ document.msFullscreenEnabled;
+}( jQuery ) );
--- /dev/null
+/**
+* hoverIntent is similar to jQuery's built-in "hover" function except that
+* instead of firing the onMouseOver event immediately, hoverIntent checks
+* to see if the user's mouse has slowed down (beneath the sensitivity
+* threshold) before firing the onMouseOver event.
+*
+* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
+* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
+*
+* hoverIntent is currently available for use in all personal or commercial
+* projects under both MIT and GPL licenses. This means that you can choose
+* the license that best suits your project, and use it accordingly.
+*
+* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
+* $("ul li").hoverIntent( showNav , hideNav );
+*
+* // advanced usage receives configuration object only
+* $("ul li").hoverIntent({
+* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
+* interval: 100, // number = milliseconds of polling interval
+* over: showNav, // function = onMouseOver callback (required)
+* timeout: 0, // number = milliseconds delay before onMouseOut function call
+* out: hideNav // function = onMouseOut callback (required)
+* });
+*
+* @param f onMouseOver function || An object with configuration options
+* @param g onMouseOut function || Nothing (use configuration options object)
+* @author Brian Cherne <brian@cherne.net>
+*/
+(function($) {
+ $.fn.hoverIntent = function(f,g) {
+ // default configuration options
+ var cfg = {
+ sensitivity: 7,
+ interval: 100,
+ timeout: 0
+ };
+ // override configuration options with user supplied object
+ cfg = $.extend(cfg, g ? { over: f, out: g } : f );
+
+ // instantiate variables
+ // cX, cY = current X and Y position of mouse, updated by mousemove event
+ // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
+ var cX, cY, pX, pY;
+
+ // A private function for getting mouse position
+ var track = function(ev) {
+ cX = ev.pageX;
+ cY = ev.pageY;
+ };
+
+ // A private function for comparing current and previous mouse position
+ var compare = function(ev,ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ // compare mouse positions to see if they've crossed the threshold
+ if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
+ $(ob).unbind("mousemove",track);
+ // set hoverIntent state to true (so mouseOut can be called)
+ ob.hoverIntent_s = 1;
+ return cfg.over.apply(ob,[ev]);
+ } else {
+ // set previous coordinates for next time
+ pX = cX; pY = cY;
+ // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
+ ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
+ }
+ };
+
+ // A private function for delaying the mouseOut function
+ var delay = function(ev,ob) {
+ ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
+ ob.hoverIntent_s = 0;
+ return cfg.out.apply(ob,[ev]);
+ };
+
+ // A private function for handling mouse 'hovering'
+ var handleHover = function(e) {
+ // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
+ var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
+ while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
+ if ( p == this ) { return false; }
+
+ // copy objects to be passed into t (required for event object to be passed in IE)
+ var ev = $.extend({},e);
+ var ob = this;
+
+ // cancel hoverIntent timer if it exists
+ if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
+
+ // else e.type == "onmouseover"
+ if (e.type == "mouseover") {
+ // set "previous" X and Y position based on initial entry point
+ pX = ev.pageX; pY = ev.pageY;
+ // update "current" X and Y position based on mousemove
+ $(ob).bind("mousemove",track);
+ // start polling interval (self-calling timeout) to compare mouse coordinates over time
+ if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
+
+ // else e.type == "onmouseout"
+ } else {
+ // unbind expensive mousemove event
+ $(ob).unbind("mousemove",track);
+ // if hoverIntent state is true, then call the mouseOut function after the specified delay
+ if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
+ }
+ };
+
+ // bind the function to the two event listeners
+ return this.mouseover(handleHover).mouseout(handleHover);
+ };
+})(jQuery);
\ No newline at end of file
--- /dev/null
+/*
+ * ----------------------------- JSTORAGE -------------------------------------
+ * Simple local storage wrapper to save data on the browser side, supporting
+ * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
+ *
+ * Author: Andris Reinman, andris.reinman@gmail.com
+ * Project homepage: www.jstorage.info
+ *
+ * Licensed under Unlicense:
+ *
+ * This is free and unencumbered software released into the public domain.
+ *
+ * Anyone is free to copy, modify, publish, use, compile, sell, or
+ * distribute this software, either in source code form or as a compiled
+ * binary, for any purpose, commercial or non-commercial, and by any
+ * means.
+ *
+ * In jurisdictions that recognize copyright laws, the author or authors
+ * of this software dedicate any and all copyright interest in the
+ * software to the public domain. We make this dedication for the benefit
+ * of the public at large and to the detriment of our heirs and
+ * successors. We intend this dedication to be an overt act of
+ * relinquishment in perpetuity of all present and future rights to this
+ * software under copyright law.
+ *
+ * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * For more information, please refer to <http://unlicense.org/>
+ */
+
+/* global ActiveXObject: false */
+/* jshint browser: true */
+
+(function() {
+ 'use strict';
+
+ var
+ /* jStorage version */
+ JSTORAGE_VERSION = '0.4.12',
+
+ /* detect a dollar object or create one if not found */
+ $ = window.jQuery || window.$ || (window.$ = {}),
+
+ /* check for a JSON handling support */
+ JSON = {
+ parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
+ String.prototype.evalJSON && function(str) {
+ return String(str).evalJSON();
+ } ||
+ $.parseJSON ||
+ $.evalJSON,
+ stringify: Object.toJSON ||
+ window.JSON && (window.JSON.stringify || window.JSON.encode) ||
+ $.toJSON
+ };
+
+ // Break if no JSON support was found
+ if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
+ throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
+ }
+
+ var
+ /* This is the object, that holds the cached values */
+ _storage = {
+ __jstorage_meta: {
+ CRC32: {}
+ }
+ },
+
+ /* Actual browser storage (localStorage or globalStorage['domain']) */
+ _storage_service = {
+ jStorage: '{}'
+ },
+
+ /* DOM element for older IE versions, holds userData behavior */
+ _storage_elm = null,
+
+ /* How much space does the storage take */
+ _storage_size = 0,
+
+ /* which backend is currently used */
+ _backend = false,
+
+ /* onchange observers */
+ _observers = {},
+
+ /* timeout to wait after onchange event */
+ _observer_timeout = false,
+
+ /* last update time */
+ _observer_update = 0,
+
+ /* pubsub observers */
+ _pubsub_observers = {},
+
+ /* skip published items older than current timestamp */
+ _pubsub_last = +new Date(),
+
+ /* Next check for TTL */
+ _ttl_timeout,
+
+ /**
+ * XML encoding and decoding as XML nodes can't be JSON'ized
+ * XML nodes are encoded and decoded if the node is the value to be saved
+ * but not if it's as a property of another object
+ * Eg. -
+ * $.jStorage.set('key', xmlNode); // IS OK
+ * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
+ */
+ _XMLService = {
+
+ /**
+ * Validates a XML node to be XML
+ * based on jQuery.isXML function
+ */
+ isXML: function(elm) {
+ var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
+ return documentElement ? documentElement.nodeName !== 'HTML' : false;
+ },
+
+ /**
+ * Encodes a XML node to string
+ * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
+ */
+ encode: function(xmlNode) {
+ if (!this.isXML(xmlNode)) {
+ return false;
+ }
+ try { // Mozilla, Webkit, Opera
+ return new XMLSerializer().serializeToString(xmlNode);
+ } catch (E1) {
+ try { // IE
+ return xmlNode.xml;
+ } catch (E2) {}
+ }
+ return false;
+ },
+
+ /**
+ * Decodes a XML node from string
+ * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
+ */
+ decode: function(xmlString) {
+ var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
+ (window.ActiveXObject && function(_xmlString) {
+ var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
+ xml_doc.async = 'false';
+ xml_doc.loadXML(_xmlString);
+ return xml_doc;
+ }),
+ resultXML;
+ if (!dom_parser) {
+ return false;
+ }
+ resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
+ return this.isXML(resultXML) ? resultXML : false;
+ }
+ };
+
+
+ ////////////////////////// PRIVATE METHODS ////////////////////////
+
+ /**
+ * Initialization function. Detects if the browser supports DOM Storage
+ * or userData behavior and behaves accordingly.
+ */
+ function _init() {
+ /* Check if browser supports localStorage */
+ var localStorageReallyWorks = false;
+ if ('localStorage' in window) {
+ try {
+ window.localStorage.setItem('_tmptest', 'tmpval');
+ localStorageReallyWorks = true;
+ window.localStorage.removeItem('_tmptest');
+ } catch (BogusQuotaExceededErrorOnIos5) {
+ // Thanks be to iOS5 Private Browsing mode which throws
+ // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
+ }
+ }
+
+ if (localStorageReallyWorks) {
+ try {
+ if (window.localStorage) {
+ _storage_service = window.localStorage;
+ _backend = 'localStorage';
+ _observer_update = _storage_service.jStorage_update;
+ }
+ } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
+ }
+ /* Check if browser supports globalStorage */
+ else if ('globalStorage' in window) {
+ try {
+ if (window.globalStorage) {
+ if (window.location.hostname == 'localhost') {
+ _storage_service = window.globalStorage['localhost.localdomain'];
+ } else {
+ _storage_service = window.globalStorage[window.location.hostname];
+ }
+ _backend = 'globalStorage';
+ _observer_update = _storage_service.jStorage_update;
+ }
+ } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
+ }
+ /* Check if browser supports userData behavior */
+ else {
+ _storage_elm = document.createElement('link');
+ if (_storage_elm.addBehavior) {
+
+ /* Use a DOM element to act as userData storage */
+ _storage_elm.style.behavior = 'url(#default#userData)';
+
+ /* userData element needs to be inserted into the DOM! */
+ document.getElementsByTagName('head')[0].appendChild(_storage_elm);
+
+ try {
+ _storage_elm.load('jStorage');
+ } catch (E) {
+ // try to reset cache
+ _storage_elm.setAttribute('jStorage', '{}');
+ _storage_elm.save('jStorage');
+ _storage_elm.load('jStorage');
+ }
+
+ var data = '{}';
+ try {
+ data = _storage_elm.getAttribute('jStorage');
+ } catch (E5) {}
+
+ try {
+ _observer_update = _storage_elm.getAttribute('jStorage_update');
+ } catch (E6) {}
+
+ _storage_service.jStorage = data;
+ _backend = 'userDataBehavior';
+ } else {
+ _storage_elm = null;
+ return;
+ }
+ }
+
+ // Load data from storage
+ _load_storage();
+
+ // remove dead keys
+ _handleTTL();
+
+ // start listening for changes
+ _setupObserver();
+
+ // initialize publish-subscribe service
+ _handlePubSub();
+
+ // handle cached navigation
+ if ('addEventListener' in window) {
+ window.addEventListener('pageshow', function(event) {
+ if (event.persisted) {
+ _storageObserver();
+ }
+ }, false);
+ }
+ }
+
+ /**
+ * Reload data from storage when needed
+ */
+ function _reloadData() {
+ var data = '{}';
+
+ if (_backend == 'userDataBehavior') {
+ _storage_elm.load('jStorage');
+
+ try {
+ data = _storage_elm.getAttribute('jStorage');
+ } catch (E5) {}
+
+ try {
+ _observer_update = _storage_elm.getAttribute('jStorage_update');
+ } catch (E6) {}
+
+ _storage_service.jStorage = data;
+ }
+
+ _load_storage();
+
+ // remove dead keys
+ _handleTTL();
+
+ _handlePubSub();
+ }
+
+ /**
+ * Sets up a storage change observer
+ */
+ function _setupObserver() {
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ if ('addEventListener' in window) {
+ window.addEventListener('storage', _storageObserver, false);
+ } else {
+ document.attachEvent('onstorage', _storageObserver);
+ }
+ } else if (_backend == 'userDataBehavior') {
+ setInterval(_storageObserver, 1000);
+ }
+ }
+
+ /**
+ * Fired on any kind of data change, needs to check if anything has
+ * really been changed
+ */
+ function _storageObserver() {
+ var updateTime;
+ // cumulate change notifications with timeout
+ clearTimeout(_observer_timeout);
+ _observer_timeout = setTimeout(function() {
+
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ updateTime = _storage_service.jStorage_update;
+ } else if (_backend == 'userDataBehavior') {
+ _storage_elm.load('jStorage');
+ try {
+ updateTime = _storage_elm.getAttribute('jStorage_update');
+ } catch (E5) {}
+ }
+
+ if (updateTime && updateTime != _observer_update) {
+ _observer_update = updateTime;
+ _checkUpdatedKeys();
+ }
+
+ }, 25);
+ }
+
+ /**
+ * Reloads the data and checks if any keys are changed
+ */
+ function _checkUpdatedKeys() {
+ var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
+ newCrc32List;
+
+ _reloadData();
+ newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
+
+ var key,
+ updated = [],
+ removed = [];
+
+ for (key in oldCrc32List) {
+ if (oldCrc32List.hasOwnProperty(key)) {
+ if (!newCrc32List[key]) {
+ removed.push(key);
+ continue;
+ }
+ if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
+ updated.push(key);
+ }
+ }
+ }
+
+ for (key in newCrc32List) {
+ if (newCrc32List.hasOwnProperty(key)) {
+ if (!oldCrc32List[key]) {
+ updated.push(key);
+ }
+ }
+ }
+
+ _fireObservers(updated, 'updated');
+ _fireObservers(removed, 'deleted');
+ }
+
+ /**
+ * Fires observers for updated keys
+ *
+ * @param {Array|String} keys Array of key names or a key
+ * @param {String} action What happened with the value (updated, deleted, flushed)
+ */
+ function _fireObservers(keys, action) {
+ keys = [].concat(keys || []);
+
+ var i, j, len, jlen;
+
+ if (action == 'flushed') {
+ keys = [];
+ for (var key in _observers) {
+ if (_observers.hasOwnProperty(key)) {
+ keys.push(key);
+ }
+ }
+ action = 'deleted';
+ }
+ for (i = 0, len = keys.length; i < len; i++) {
+ if (_observers[keys[i]]) {
+ for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
+ _observers[keys[i]][j](keys[i], action);
+ }
+ }
+ if (_observers['*']) {
+ for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
+ _observers['*'][j](keys[i], action);
+ }
+ }
+ }
+ }
+
+ /**
+ * Publishes key change to listeners
+ */
+ function _publishChange() {
+ var updateTime = (+new Date()).toString();
+
+ if (_backend == 'localStorage' || _backend == 'globalStorage') {
+ try {
+ _storage_service.jStorage_update = updateTime;
+ } catch (E8) {
+ // safari private mode has been enabled after the jStorage initialization
+ _backend = false;
+ }
+ } else if (_backend == 'userDataBehavior') {
+ _storage_elm.setAttribute('jStorage_update', updateTime);
+ _storage_elm.save('jStorage');
+ }
+
+ _storageObserver();
+ }
+
+ /**
+ * Loads the data from the storage based on the supported mechanism
+ */
+ function _load_storage() {
+ /* if jStorage string is retrieved, then decode it */
+ if (_storage_service.jStorage) {
+ try {
+ _storage = JSON.parse(String(_storage_service.jStorage));
+ } catch (E6) {
+ _storage_service.jStorage = '{}';
+ }
+ } else {
+ _storage_service.jStorage = '{}';
+ }
+ _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
+
+ if (!_storage.__jstorage_meta) {
+ _storage.__jstorage_meta = {};
+ }
+ if (!_storage.__jstorage_meta.CRC32) {
+ _storage.__jstorage_meta.CRC32 = {};
+ }
+ }
+
+ /**
+ * This functions provides the 'save' mechanism to store the jStorage object
+ */
+ function _save() {
+ _dropOldEvents(); // remove expired events
+ try {
+ _storage_service.jStorage = JSON.stringify(_storage);
+ // If userData is used as the storage engine, additional
+ if (_storage_elm) {
+ _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
+ _storage_elm.save('jStorage');
+ }
+ _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
+ } catch (E7) { /* probably cache is full, nothing is saved this way*/ }
+ }
+
+ /**
+ * Function checks if a key is set and is string or numberic
+ *
+ * @param {String} key Key name
+ */
+ function _checkKey(key) {
+ if (typeof key != 'string' && typeof key != 'number') {
+ throw new TypeError('Key name must be string or numeric');
+ }
+ if (key == '__jstorage_meta') {
+ throw new TypeError('Reserved key name');
+ }
+ return true;
+ }
+
+ /**
+ * Removes expired keys
+ */
+ function _handleTTL() {
+ var curtime, i, TTL, CRC32, nextExpire = Infinity,
+ changed = false,
+ deleted = [];
+
+ clearTimeout(_ttl_timeout);
+
+ if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
+ // nothing to do here
+ return;
+ }
+
+ curtime = +new Date();
+ TTL = _storage.__jstorage_meta.TTL;
+
+ CRC32 = _storage.__jstorage_meta.CRC32;
+ for (i in TTL) {
+ if (TTL.hasOwnProperty(i)) {
+ if (TTL[i] <= curtime) {
+ delete TTL[i];
+ delete CRC32[i];
+ delete _storage[i];
+ changed = true;
+ deleted.push(i);
+ } else if (TTL[i] < nextExpire) {
+ nextExpire = TTL[i];
+ }
+ }
+ }
+
+ // set next check
+ if (nextExpire != Infinity) {
+ _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
+ }
+
+ // save changes
+ if (changed) {
+ _save();
+ _publishChange();
+ _fireObservers(deleted, 'deleted');
+ }
+ }
+
+ /**
+ * Checks if there's any events on hold to be fired to listeners
+ */
+ function _handlePubSub() {
+ var i, len;
+ if (!_storage.__jstorage_meta.PubSub) {
+ return;
+ }
+ var pubelm,
+ _pubsubCurrent = _pubsub_last,
+ needFired = [];
+
+ for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
+ pubelm = _storage.__jstorage_meta.PubSub[i];
+ if (pubelm[0] > _pubsub_last) {
+ _pubsubCurrent = pubelm[0];
+ needFired.unshift(pubelm);
+ }
+ }
+
+ for (i = needFired.length - 1; i >= 0; i--) {
+ _fireSubscribers(needFired[i][1], needFired[i][2]);
+ }
+
+ _pubsub_last = _pubsubCurrent;
+ }
+
+ /**
+ * Fires all subscriber listeners for a pubsub channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload data to deliver
+ */
+ function _fireSubscribers(channel, payload) {
+ if (_pubsub_observers[channel]) {
+ for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
+ // send immutable data that can't be modified by listeners
+ try {
+ _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
+ } catch (E) {}
+ }
+ }
+ }
+
+ /**
+ * Remove old events from the publish stream (at least 2sec old)
+ */
+ function _dropOldEvents() {
+ if (!_storage.__jstorage_meta.PubSub) {
+ return;
+ }
+
+ var retire = +new Date() - 2000;
+
+ for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
+ if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
+ // deleteCount is needed for IE6
+ _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
+ break;
+ }
+ }
+
+ if (!_storage.__jstorage_meta.PubSub.length) {
+ delete _storage.__jstorage_meta.PubSub;
+ }
+
+ }
+
+ /**
+ * Publish payload to a channel
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to send to the subscribers
+ */
+ function _publish(channel, payload) {
+ if (!_storage.__jstorage_meta) {
+ _storage.__jstorage_meta = {};
+ }
+ if (!_storage.__jstorage_meta.PubSub) {
+ _storage.__jstorage_meta.PubSub = [];
+ }
+
+ _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
+
+ _save();
+ _publishChange();
+ }
+
+
+ /**
+ * JS Implementation of MurmurHash2
+ *
+ * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
+ *
+ * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
+ * @see http://github.com/garycourt/murmurhash-js
+ * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
+ * @see http://sites.google.com/site/murmurhash/
+ *
+ * @param {string} str ASCII only
+ * @param {number} seed Positive integer only
+ * @return {number} 32-bit positive integer hash
+ */
+
+ function murmurhash2_32_gc(str, seed) {
+ var
+ l = str.length,
+ h = seed ^ l,
+ i = 0,
+ k;
+
+ while (l >= 4) {
+ k =
+ ((str.charCodeAt(i) & 0xff)) |
+ ((str.charCodeAt(++i) & 0xff) << 8) |
+ ((str.charCodeAt(++i) & 0xff) << 16) |
+ ((str.charCodeAt(++i) & 0xff) << 24);
+
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ k ^= k >>> 24;
+ k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
+
+ l -= 4;
+ ++i;
+ }
+
+ switch (l) {
+ case 3:
+ h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
+ /* falls through */
+ case 2:
+ h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
+ /* falls through */
+ case 1:
+ h ^= (str.charCodeAt(i) & 0xff);
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ }
+
+ h ^= h >>> 13;
+ h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
+ h ^= h >>> 15;
+
+ return h >>> 0;
+ }
+
+ ////////////////////////// PUBLIC INTERFACE /////////////////////////
+
+ $.jStorage = {
+ /* Version number */
+ version: JSTORAGE_VERSION,
+
+ /**
+ * Sets a key's value.
+ *
+ * @param {String} key Key to set. If this value is not set or not
+ * a string an exception is raised.
+ * @param {Mixed} value Value to set. This can be any value that is JSON
+ * compatible (Numbers, Strings, Objects etc.).
+ * @param {Object} [options] - possible options to use
+ * @param {Number} [options.TTL] - optional TTL value, in milliseconds
+ * @return {Mixed} the used value
+ */
+ set: function(key, value, options) {
+ _checkKey(key);
+
+ options = options || {};
+
+ // undefined values are deleted automatically
+ if (typeof value == 'undefined') {
+ this.deleteKey(key);
+ return value;
+ }
+
+ if (_XMLService.isXML(value)) {
+ value = {
+ _is_xml: true,
+ xml: _XMLService.encode(value)
+ };
+ } else if (typeof value == 'function') {
+ return undefined; // functions can't be saved!
+ } else if (value && typeof value == 'object') {
+ // clone the object before saving to _storage tree
+ value = JSON.parse(JSON.stringify(value));
+ }
+
+ _storage[key] = value;
+
+ _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
+
+ this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
+
+ _fireObservers(key, 'updated');
+ return value;
+ },
+
+ /**
+ * Looks up a key in cache
+ *
+ * @param {String} key - Key to look up.
+ * @param {mixed} def - Default value to return, if key didn't exist.
+ * @return {Mixed} the key value, default value or null
+ */
+ get: function(key, def) {
+ _checkKey(key);
+ if (key in _storage) {
+ if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
+ return _XMLService.decode(_storage[key].xml);
+ } else {
+ return _storage[key];
+ }
+ }
+ return typeof(def) == 'undefined' ? null : def;
+ },
+
+ /**
+ * Deletes a key from cache.
+ *
+ * @param {String} key - Key to delete.
+ * @return {Boolean} true if key existed or false if it didn't
+ */
+ deleteKey: function(key) {
+ _checkKey(key);
+ if (key in _storage) {
+ delete _storage[key];
+ // remove from TTL list
+ if (typeof _storage.__jstorage_meta.TTL == 'object' &&
+ key in _storage.__jstorage_meta.TTL) {
+ delete _storage.__jstorage_meta.TTL[key];
+ }
+
+ delete _storage.__jstorage_meta.CRC32[key];
+
+ _save();
+ _publishChange();
+ _fireObservers(key, 'deleted');
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Sets a TTL for a key, or remove it if ttl value is 0 or below
+ *
+ * @param {String} key - key to set the TTL for
+ * @param {Number} ttl - TTL timeout in milliseconds
+ * @return {Boolean} true if key existed or false if it didn't
+ */
+ setTTL: function(key, ttl) {
+ var curtime = +new Date();
+ _checkKey(key);
+ ttl = Number(ttl) || 0;
+ if (key in _storage) {
+
+ if (!_storage.__jstorage_meta.TTL) {
+ _storage.__jstorage_meta.TTL = {};
+ }
+
+ // Set TTL value for the key
+ if (ttl > 0) {
+ _storage.__jstorage_meta.TTL[key] = curtime + ttl;
+ } else {
+ delete _storage.__jstorage_meta.TTL[key];
+ }
+
+ _save();
+
+ _handleTTL();
+
+ _publishChange();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
+ *
+ * @param {String} key Key to check
+ * @return {Number} Remaining TTL in milliseconds
+ */
+ getTTL: function(key) {
+ var curtime = +new Date(),
+ ttl;
+ _checkKey(key);
+ if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
+ ttl = _storage.__jstorage_meta.TTL[key] - curtime;
+ return ttl || 0;
+ }
+ return 0;
+ },
+
+ /**
+ * Deletes everything in cache.
+ *
+ * @return {Boolean} Always true
+ */
+ flush: function() {
+ _storage = {
+ __jstorage_meta: {
+ CRC32: {}
+ }
+ };
+ _save();
+ _publishChange();
+ _fireObservers(null, 'flushed');
+ return true;
+ },
+
+ /**
+ * Returns a read-only copy of _storage
+ *
+ * @return {Object} Read-only copy of _storage
+ */
+ storageObj: function() {
+ function F() {}
+ F.prototype = _storage;
+ return new F();
+ },
+
+ /**
+ * Returns an index of all used keys as an array
+ * ['key1', 'key2',..'keyN']
+ *
+ * @return {Array} Used keys
+ */
+ index: function() {
+ var index = [],
+ i;
+ for (i in _storage) {
+ if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
+ index.push(i);
+ }
+ }
+ return index;
+ },
+
+ /**
+ * How much space in bytes does the storage take?
+ *
+ * @return {Number} Storage size in chars (not the same as in bytes,
+ * since some chars may take several bytes)
+ */
+ storageSize: function() {
+ return _storage_size;
+ },
+
+ /**
+ * Which backend is currently in use?
+ *
+ * @return {String} Backend name
+ */
+ currentBackend: function() {
+ return _backend;
+ },
+
+ /**
+ * Test if storage is available
+ *
+ * @return {Boolean} True if storage can be used
+ */
+ storageAvailable: function() {
+ return !!_backend;
+ },
+
+ /**
+ * Register change listeners
+ *
+ * @param {String} key Key name
+ * @param {Function} callback Function to run when the key changes
+ */
+ listenKeyChange: function(key, callback) {
+ _checkKey(key);
+ if (!_observers[key]) {
+ _observers[key] = [];
+ }
+ _observers[key].push(callback);
+ },
+
+ /**
+ * Remove change listeners
+ *
+ * @param {String} key Key name to unregister listeners against
+ * @param {Function} [callback] If set, unregister the callback, if not - unregister all
+ */
+ stopListening: function(key, callback) {
+ _checkKey(key);
+
+ if (!_observers[key]) {
+ return;
+ }
+
+ if (!callback) {
+ delete _observers[key];
+ return;
+ }
+
+ for (var i = _observers[key].length - 1; i >= 0; i--) {
+ if (_observers[key][i] == callback) {
+ _observers[key].splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Subscribe to a Publish/Subscribe event stream
+ *
+ * @param {String} channel Channel name
+ * @param {Function} callback Function to run when the something is published to the channel
+ */
+ subscribe: function(channel, callback) {
+ channel = (channel || '').toString();
+ if (!channel) {
+ throw new TypeError('Channel not defined');
+ }
+ if (!_pubsub_observers[channel]) {
+ _pubsub_observers[channel] = [];
+ }
+ _pubsub_observers[channel].push(callback);
+ },
+
+ /**
+ * Publish data to an event stream
+ *
+ * @param {String} channel Channel name
+ * @param {Mixed} payload Payload to deliver
+ */
+ publish: function(channel, payload) {
+ channel = (channel || '').toString();
+ if (!channel) {
+ throw new TypeError('Channel not defined');
+ }
+
+ _publish(channel, payload);
+ },
+
+ /**
+ * Reloads the data from browser storage
+ */
+ reInit: function() {
+ _reloadData();
+ },
+
+ /**
+ * Removes reference from global objects and saves it as jStorage
+ *
+ * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
+ */
+ noConflict: function(saveInGlobal) {
+ delete window.$.jStorage;
+
+ if (saveInGlobal) {
+ window.jStorage = this;
+ }
+
+ return this;
+ }
+ };
+
+ // Initialize jStorage
+ _init();
+
+})();
--- /dev/null
+/*!
+ * MockJax - jQuery Plugin to Mock Ajax requests
+ *
+ * Version: 1.4.0
+ * Released: 2011-02-04
+ * Source: http://github.com/appendto/jquery-mockjax
+ * Docs: http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development
+ * Plugin: mockjax
+ * Author: Jonathan Sharp (http://jdsharp.com)
+ * License: MIT,GPL
+ *
+ * Copyright (c) 2010 appendTo LLC.
+ * Dual licensed under the MIT or GPL licenses.
+ * http://appendto.com/open-source-licenses
+ */
+(function($) {
+ var _ajax = $.ajax,
+ mockHandlers = [];
+
+ function parseXML(xml) {
+ if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
+ DOMParser = function() { };
+ DOMParser.prototype.parseFromString = function( xmlString ) {
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML( xmlString );
+ return doc;
+ };
+ }
+
+ try {
+ var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
+ if ( $.isXMLDoc( xmlDoc ) ) {
+ var err = $('parsererror', xmlDoc);
+ if ( err.length == 1 ) {
+ throw('Error: ' + $(xmlDoc).text() );
+ }
+ } else {
+ throw('Unable to parse XML');
+ }
+ } catch( e ) {
+ var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+ $(document).trigger('xmlParseError', [ msg ]);
+ return undefined;
+ }
+ return xmlDoc;
+ }
+
+ $.extend({
+ ajax: function(origSettings) {
+ var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
+ mock = false;
+ // Iterate over our mock handlers (in registration order) until we find
+ // one that is willing to intercept the request
+ $.each(mockHandlers, function(k, v) {
+ if ( !mockHandlers[k] ) {
+ return;
+ }
+ var m = null;
+ // If the mock was registered with a function, let the function decide if we
+ // want to mock this request
+ if ( $.isFunction(mockHandlers[k]) ) {
+ m = mockHandlers[k](s);
+ } else {
+ m = mockHandlers[k];
+ // Inspect the URL of the request and check if the mock handler's url
+ // matches the url for this ajax request
+ if ( $.isFunction(m.url.test) ) {
+ // The user provided a regex for the url, test it
+ if ( !m.url.test( s.url ) ) {
+ m = null;
+ }
+ } else {
+ // Look for a simple wildcard '*' or a direct URL match
+ var star = m.url.indexOf('*');
+ if ( ( m.url != '*' && m.url != s.url && star == -1 ) ||
+ ( star > -1 && m.url.substr(0, star) != s.url.substr(0, star) ) ) {
+ // The url we tested did not match the wildcard *
+ m = null;
+ }
+ }
+ if ( m ) {
+ // Inspect the data submitted in the request (either POST body or GET query string)
+ if ( m.data && s.data ) {
+ var identical = false;
+ // Deep inspect the identity of the objects
+ (function ident(mock, live) {
+ // Test for situations where the data is a querystring (not an object)
+ if (typeof live === 'string') {
+ // Querystring may be a regex
+ identical = $.isFunction( mock.test ) ? mock.test(live) : mock == live;
+ return identical;
+ }
+ $.each(mock, function(k, v) {
+ if ( live[k] === undefined ) {
+ identical = false;
+ return false;
+ } else {
+ identical = true;
+ if ( typeof live[k] == 'object' ) {
+ return ident(mock[k], live[k]);
+ } else {
+ if ( $.isFunction( mock[k].test ) ) {
+ identical = mock[k].test(live[k]);
+ } else {
+ identical = ( mock[k] == live[k] );
+ }
+ return identical;
+ }
+ }
+ });
+ })(m.data, s.data);
+ // They're not identical, do not mock this request
+ if ( identical == false ) {
+ m = null;
+ }
+ }
+ // Inspect the request type
+ if ( m && m.type && m.type != s.type ) {
+ // The request type doesn't match (GET vs. POST)
+ m = null;
+ }
+ }
+ }
+ if ( m ) {
+ mock = true;
+
+ // Handle console logging
+ var c = $.extend({}, $.mockjaxSettings, m);
+ if ( c.log && $.isFunction(c.log) ) {
+ c.log('MOCK ' + s.type.toUpperCase() + ': ' + s.url, $.extend({}, s));
+ }
+
+ var jsre = /=\?(&|$)/, jsc = (new Date()).getTime();
+
+ // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
+ // because there isn't an easy hook for the cross domain script tag of jsonp
+ if ( s.dataType === "jsonp" ) {
+ if ( s.type.toUpperCase() === "GET" ) {
+ if ( !jsre.test( s.url ) ) {
+ s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
+ }
+ } else if ( !s.data || !jsre.test(s.data) ) {
+ s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
+ }
+ s.dataType = "json";
+ }
+
+ // Build temporary JSONP function
+ if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
+ jsonp = s.jsonpCallback || ("jsonp" + jsc++);
+
+ // Replace the =? sequence both in the query string and the data
+ if ( s.data ) {
+ s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
+ }
+
+ s.url = s.url.replace(jsre, "=" + jsonp + "$1");
+
+ // We need to make sure
+ // that a JSONP style response is executed properly
+ s.dataType = "script";
+
+ // Handle JSONP-style loading
+ window[ jsonp ] = window[ jsonp ] || function( tmp ) {
+ data = tmp;
+ success();
+ complete();
+ // Garbage collect
+ window[ jsonp ] = undefined;
+
+ try {
+ delete window[ jsonp ];
+ } catch(e) {}
+
+ if ( head ) {
+ head.removeChild( script );
+ }
+ };
+ }
+
+ var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+ parts = rurl.exec( s.url ),
+ remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+ // Test if we are going to create a script tag (if so, intercept & mock)
+ if ( s.dataType === "script" && s.type.toUpperCase() === "GET" && remote ) {
+ // Synthesize the mock request for adding a script tag
+ var callbackContext = origSettings && origSettings.context || s;
+
+ function success() {
+ // If a local callback was specified, fire it and pass it the data
+ if ( s.success ) {
+ s.success.call( callbackContext, ( m.response ? m.response.toString() : m.responseText || ''), status, {} );
+ }
+
+ // Fire the global callback
+ if ( s.global ) {
+ trigger( "ajaxSuccess", [{}, s] );
+ }
+ }
+
+ function complete() {
+ // Process result
+ if ( s.complete ) {
+ s.complete.call( callbackContext, {} , status );
+ }
+
+ // The request was completed
+ if ( s.global ) {
+ trigger( "ajaxComplete", [{}, s] );
+ }
+
+ // Handle the global AJAX counter
+ if ( s.global && ! --jQuery.active ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+
+ function trigger(type, args) {
+ (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
+ }
+
+ if ( m.response && $.isFunction(m.response) ) {
+ m.response(origSettings);
+ } else {
+ $.globalEval(m.responseText);
+ }
+ success();
+ complete();
+ return false;
+ }
+ mock = _ajax.call($, $.extend(true, {}, origSettings, {
+ // Mock the XHR object
+ xhr: function() {
+ // Extend with our default mockjax settings
+ m = $.extend({}, $.mockjaxSettings, m);
+
+ if ( m.contentType ) {
+ m.headers['content-type'] = m.contentType;
+ }
+
+ // Return our mock xhr object
+ return {
+ status: m.status,
+ readyState: 1,
+ open: function() { },
+ send: function() {
+ // This is a substitute for < 1.4 which lacks $.proxy
+ var process = (function(that) {
+ return function() {
+ return (function() {
+ // The request has returned
+ this.status = m.status;
+ this.readyState = 4;
+
+ // We have an executable function, call it to give
+ // the mock handler a chance to update it's data
+ if ( $.isFunction(m.response) ) {
+ m.response(origSettings);
+ }
+ // Copy over our mock to our xhr object before passing control back to
+ // jQuery's onreadystatechange callback
+ if ( s.dataType == 'json' && ( typeof m.responseText == 'object' ) ) {
+ this.responseText = JSON.stringify(m.responseText);
+ } else if ( s.dataType == 'xml' ) {
+ if ( typeof m.responseXML == 'string' ) {
+ this.responseXML = parseXML(m.responseXML);
+ } else {
+ this.responseXML = m.responseXML;
+ }
+ } else {
+ this.responseText = m.responseText;
+ }
+ // jQuery < 1.4 doesn't have onreadystate change for xhr
+ if ( $.isFunction(this.onreadystatechange) ) {
+ this.onreadystatechange( m.isTimeout ? 'timeout' : undefined );
+ }
+ }).apply(that);
+ };
+ })(this);
+
+ if ( m.proxy ) {
+ // We're proxying this request and loading in an external file instead
+ _ajax({
+ global: false,
+ url: m.proxy,
+ type: m.proxyType,
+ data: m.data,
+ dataType: s.dataType,
+ complete: function(xhr, txt) {
+ m.responseXML = xhr.responseXML;
+ m.responseText = xhr.responseText;
+ this.responseTimer = setTimeout(process, m.responseTime || 0);
+ }
+ });
+ } else {
+ // type == 'POST' || 'GET' || 'DELETE'
+ if ( s.async === false ) {
+ // TODO: Blocking delay
+ process();
+ } else {
+ this.responseTimer = setTimeout(process, m.responseTime || 50);
+ }
+ }
+ },
+ abort: function() {
+ clearTimeout(this.responseTimer);
+ },
+ setRequestHeader: function() { },
+ getResponseHeader: function(header) {
+ // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
+ if ( m.headers && m.headers[header] ) {
+ // Return arbitrary headers
+ return m.headers[header];
+ } else if ( header.toLowerCase() == 'last-modified' ) {
+ return m.lastModified || (new Date()).toString();
+ } else if ( header.toLowerCase() == 'etag' ) {
+ return m.etag || '';
+ } else if ( header.toLowerCase() == 'content-type' ) {
+ return m.contentType || 'text/plain';
+ }
+ },
+ getAllResponseHeaders: function() {
+ var headers = '';
+ $.each(m.headers, function(k, v) {
+ headers += k + ': ' + v + "\n";
+ });
+ return headers;
+ }
+ };
+ }
+ }));
+ return false;
+ }
+ });
+ // We don't have a mock request, trigger a normal request
+ if ( !mock ) {
+ return _ajax.apply($, arguments);
+ } else {
+ return mock;
+ }
+ }
+ });
+
+ $.mockjaxSettings = {
+ //url: null,
+ //type: 'GET',
+ log: function(msg) {
+ window['console'] && window.console.log && window.console.log(msg);
+ },
+ status: 200,
+ responseTime: 500,
+ isTimeout: false,
+ contentType: 'text/plain',
+ response: '',
+ responseText: '',
+ responseXML: '',
+ proxy: '',
+ proxyType: 'GET',
+
+ lastModified: null,
+ etag: '',
+ headers: {
+ etag: 'IJF@H#@923uf8023hFO@I#H#',
+ 'content-type' : 'text/plain'
+ }
+ };
+
+ $.mockjax = function(settings) {
+ var i = mockHandlers.length;
+ mockHandlers[i] = settings;
+ return i;
+ };
+ $.mockjaxClear = function(i) {
+ if ( arguments.length == 1 ) {
+ mockHandlers[i] = null;
+ } else {
+ mockHandlers = [];
+ }
+ };
+})(jQuery);
--- /dev/null
+/*!
+ * jQuery xmlDOM Plugin v1.0
+ * http://outwestmedia.com/jquery-plugins/xmldom/
+ *
+ * Released: 2009-04-06
+ * Version: 1.0
+ *
+ * Copyright (c) 2009 Jonathan Sharp, Out West Media LLC.
+ * Dual licensed under the MIT and GPL licenses.
+ * http://docs.jquery.com/License
+ */
+(function($) {
+ // IE DOMParser wrapper
+ if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
+ DOMParser = function() { };
+ DOMParser.prototype.parseFromString = function( xmlString ) {
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.async = 'false';
+ doc.loadXML( xmlString );
+ return doc;
+ };
+ }
+
+ $.xmlDOM = function(xml, onErrorFn) {
+ try {
+ var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
+ if ( $.isXMLDoc( xmlDoc ) ) {
+ var err = $('parsererror', xmlDoc);
+ if ( err.length == 1 ) {
+ throw('Error: ' + $(xmlDoc).text() );
+ }
+ } else {
+ throw('Unable to parse XML');
+ }
+ } catch( e ) {
+ var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+ if ( $.isFunction( onErrorFn ) ) {
+ onErrorFn( msg );
+ } else {
+ $(document).trigger('xmlParseError', [ msg ]);
+ }
+ return $([]);
+ }
+ return $( xmlDoc );
+ };
+})(jQuery);
\ No newline at end of file
+++ /dev/null
-/*
- * jQuery Asynchronous Plugin 1.0
- *
- * Copyright (c) 2008 Vincent Robert (genezys.net)
- * Dual licensed under the MIT (MIT-LICENSE.txt)
- * and GPL (GPL-LICENSE.txt) licenses.
- *
- */
-(function($){
-
-// opts.delay : (default 10) delay between async call in ms
-// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
-// opts.test : (default true) function to test in the while test part
-// opts.loop : (default empty) function to call in the while loop part
-// opts.end : (default empty) function to call at the end of the while loop
-$.whileAsync = function(opts) {
- var delay = Math.abs(opts.delay) || 10,
- bulk = isNaN(opts.bulk) ? 500 : Math.abs(opts.bulk),
- test = opts.test || function(){ return true; },
- loop = opts.loop || function(){},
- end = opts.end || function(){};
-
- (function(){
-
- var t = false,
- begin = new Date();
-
- while( t = test() ) {
- loop();
- if( bulk === 0 || (new Date() - begin) > bulk ) {
- break;
- }
- }
- if( t ) {
- setTimeout(arguments.callee, delay);
- }
- else {
- end();
- }
-
- })();
-};
-
-// opts.delay : (default 10) delay between async call in ms
-// opts.bulk : (default 500) delay during which the loop can continue synchronously without yielding the CPU
-// opts.loop : (default empty) function to call in the each loop part, signature: function(index, value) this = value
-// opts.end : (default empty) function to call at the end of the each loop
-$.eachAsync = function(array, opts) {
- var i = 0,
- l = array.length,
- loop = opts.loop || function(){};
-
- $.whileAsync(
- $.extend(opts, {
- test: function() { return i < l; },
- loop: function() {
- var val = array[i];
- return loop.call(val, i++, val);
- }
- })
- );
-};
-
-$.fn.eachAsync = function(opts) {
- $.eachAsync(this, opts);
- return this;
-}
-
-})(jQuery);
\ No newline at end of file
+++ /dev/null
-/*!
- * jQuery throttle / debounce - v1.1 - 3/7/2010
- * http://benalman.com/projects/jquery-throttle-debounce-plugin/
- *
- * Copyright (c) 2010 "Cowboy" Ben Alman
- * Dual licensed under the MIT and GPL licenses.
- * http://benalman.com/about/license/
- */
-
-// Script: jQuery throttle / debounce: Sometimes, less is more!
-//
-// *Version: 1.1, Last updated: 3/7/2010*
-//
-// Project Home - http://benalman.com/projects/jquery-throttle-debounce-plugin/
-// GitHub - http://github.com/cowboy/jquery-throttle-debounce/
-// Source - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.js
-// (Minified) - http://github.com/cowboy/jquery-throttle-debounce/raw/master/jquery.ba-throttle-debounce.min.js (0.7kb)
-//
-// About: License
-//
-// Copyright (c) 2010 "Cowboy" Ben Alman,
-// Dual licensed under the MIT and GPL licenses.
-// http://benalman.com/about/license/
-//
-// About: Examples
-//
-// These working examples, complete with fully commented code, illustrate a few
-// ways in which this plugin can be used.
-//
-// Throttle - http://benalman.com/code/projects/jquery-throttle-debounce/examples/throttle/
-// Debounce - http://benalman.com/code/projects/jquery-throttle-debounce/examples/debounce/
-//
-// About: Support and Testing
-//
-// Information about what version or versions of jQuery this plugin has been
-// tested with, what browsers it has been tested in, and where the unit tests
-// reside (so you can test it yourself).
-//
-// jQuery Versions - none, 1.3.2, 1.4.2
-// Browsers Tested - Internet Explorer 6-8, Firefox 2-3.6, Safari 3-4, Chrome 4-5, Opera 9.6-10.1.
-// Unit Tests - http://benalman.com/code/projects/jquery-throttle-debounce/unit/
-//
-// About: Release History
-//
-// 1.1 - (3/7/2010) Fixed a bug in <jQuery.throttle> where trailing callbacks
-// executed later than they should. Reworked a fair amount of internal
-// logic as well.
-// 1.0 - (3/6/2010) Initial release as a stand-alone project. Migrated over
-// from jquery-misc repo v0.4 to jquery-throttle repo v1.0, added the
-// no_trailing throttle parameter and debounce functionality.
-//
-// Topic: Note for non-jQuery users
-//
-// jQuery isn't actually required for this plugin, because nothing internal
-// uses any jQuery methods or properties. jQuery is just used as a namespace
-// under which these methods can exist.
-//
-// Since jQuery isn't actually required for this plugin, if jQuery doesn't exist
-// when this plugin is loaded, the method described below will be created in
-// the `Cowboy` namespace. Usage will be exactly the same, but instead of
-// $.method() or jQuery.method(), you'll need to use Cowboy.method().
-
-(function(window,undefined){
- '$:nomunge'; // Used by YUI compressor.
-
- // Since jQuery really isn't required for this plugin, use `jQuery` as the
- // namespace only if it already exists, otherwise use the `Cowboy` namespace,
- // creating it if necessary.
- var $ = window.jQuery || window.Cowboy || ( window.Cowboy = {} ),
-
- // Internal method reference.
- jq_throttle;
-
- // Method: jQuery.throttle
- //
- // Throttle execution of a function. Especially useful for rate limiting
- // execution of handlers on events like resize and scroll. If you want to
- // rate-limit execution of a function to a single time, see the
- // <jQuery.debounce> method.
- //
- // In this visualization, | is a throttled-function call and X is the actual
- // callback execution:
- //
- // > Throttled with `no_trailing` specified as false or unspecified:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X X X X X X X X X X X
- // >
- // > Throttled with `no_trailing` specified as true:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X X X X X X X X X
- //
- // Usage:
- //
- // > var throttled = jQuery.throttle( delay, [ no_trailing, ] callback );
- // >
- // > jQuery('selector').bind( 'someevent', throttled );
- // > jQuery('selector').unbind( 'someevent', throttled );
- //
- // This also works in jQuery 1.4+:
- //
- // > jQuery('selector').bind( 'someevent', jQuery.throttle( delay, [ no_trailing, ] callback ) );
- // > jQuery('selector').unbind( 'someevent', callback );
- //
- // Arguments:
- //
- // delay - (Number) A zero-or-greater delay in milliseconds. For event
- // callbacks, values around 100 or 250 (or even higher) are most useful.
- // no_trailing - (Boolean) Optional, defaults to false. If no_trailing is
- // true, callback will only execute every `delay` milliseconds while the
- // throttled-function is being called. If no_trailing is false or
- // unspecified, callback will be executed one final time after the last
- // throttled-function call. (After the throttled-function has not been
- // called for `delay` milliseconds, the internal counter is reset)
- // callback - (Function) A function to be executed after delay milliseconds.
- // The `this` context and all arguments are passed through, as-is, to
- // `callback` when the throttled-function is executed.
- //
- // Returns:
- //
- // (Function) A new, throttled, function.
-
- $.throttle = jq_throttle = function( delay, no_trailing, callback, debounce_mode ) {
- // After wrapper has stopped being called, this timeout ensures that
- // `callback` is executed at the proper times in `throttle` and `end`
- // debounce modes.
- var timeout_id,
-
- // Keep track of the last time `callback` was executed.
- last_exec = 0;
-
- // `no_trailing` defaults to falsy.
- if ( typeof no_trailing !== 'boolean' ) {
- debounce_mode = callback;
- callback = no_trailing;
- no_trailing = undefined;
- }
-
- // The `wrapper` function encapsulates all of the throttling / debouncing
- // functionality and when executed will limit the rate at which `callback`
- // is executed.
- function wrapper() {
- var that = this,
- elapsed = +new Date() - last_exec,
- args = arguments;
-
- // Execute `callback` and update the `last_exec` timestamp.
- function exec() {
- last_exec = +new Date();
- callback.apply( that, args );
- };
-
- // If `debounce_mode` is true (at_begin) this is used to clear the flag
- // to allow future `callback` executions.
- function clear() {
- timeout_id = undefined;
- };
-
- if ( debounce_mode && !timeout_id ) {
- // Since `wrapper` is being called for the first time and
- // `debounce_mode` is true (at_begin), execute `callback`.
- exec();
- }
-
- // Clear any existing timeout.
- timeout_id && clearTimeout( timeout_id );
-
- if ( debounce_mode === undefined && elapsed > delay ) {
- // In throttle mode, if `delay` time has been exceeded, execute
- // `callback`.
- exec();
-
- } else if ( no_trailing !== true ) {
- // In trailing throttle mode, since `delay` time has not been
- // exceeded, schedule `callback` to execute `delay` ms after most
- // recent execution.
- //
- // If `debounce_mode` is true (at_begin), schedule `clear` to execute
- // after `delay` ms.
- //
- // If `debounce_mode` is false (at end), schedule `callback` to
- // execute after `delay` ms.
- timeout_id = setTimeout( debounce_mode ? clear : exec, debounce_mode === undefined ? delay - elapsed : delay );
- }
- };
-
- // Set the guid of `wrapper` function to the same of original callback, so
- // it can be removed in jQuery 1.4+ .unbind or .die by using the original
- // callback as a reference.
- if ( $.guid ) {
- wrapper.guid = callback.guid = callback.guid || $.guid++;
- }
-
- // Return the wrapper function.
- return wrapper;
- };
-
- // Method: jQuery.debounce
- //
- // Debounce execution of a function. Debouncing, unlike throttling,
- // guarantees that a function is only executed a single time, either at the
- // very beginning of a series of calls, or at the very end. If you want to
- // simply rate-limit execution of a function, see the <jQuery.throttle>
- // method.
- //
- // In this visualization, | is a debounced-function call and X is the actual
- // callback execution:
- //
- // > Debounced with `at_begin` specified as false or unspecified:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X
- // >
- // > Debounced with `at_begin` specified as true:
- // > ||||||||||||||||||||||||| (pause) |||||||||||||||||||||||||
- // > X X
- //
- // Usage:
- //
- // > var debounced = jQuery.debounce( delay, [ at_begin, ] callback );
- // >
- // > jQuery('selector').bind( 'someevent', debounced );
- // > jQuery('selector').unbind( 'someevent', debounced );
- //
- // This also works in jQuery 1.4+:
- //
- // > jQuery('selector').bind( 'someevent', jQuery.debounce( delay, [ at_begin, ] callback ) );
- // > jQuery('selector').unbind( 'someevent', callback );
- //
- // Arguments:
- //
- // delay - (Number) A zero-or-greater delay in milliseconds. For event
- // callbacks, values around 100 or 250 (or even higher) are most useful.
- // at_begin - (Boolean) Optional, defaults to false. If at_begin is false or
- // unspecified, callback will only be executed `delay` milliseconds after
- // the last debounced-function call. If at_begin is true, callback will be
- // executed only at the first debounced-function call. (After the
- // throttled-function has not been called for `delay` milliseconds, the
- // internal counter is reset)
- // callback - (Function) A function to be executed after delay milliseconds.
- // The `this` context and all arguments are passed through, as-is, to
- // `callback` when the debounced-function is executed.
- //
- // Returns:
- //
- // (Function) A new, debounced, function.
-
- $.debounce = function( delay, at_begin, callback ) {
- return callback === undefined
- ? jq_throttle( delay, at_begin, false )
- : jq_throttle( delay, callback, at_begin !== false );
- };
-
-})(this);
+++ /dev/null
-/*!
- * jQuery Cookie Plugin v1.3.1
- * https://github.com/carhartl/jquery-cookie
- *
- * Copyright 2013 Klaus Hartl
- * Released under the MIT license
- */
-(function ($, document, undefined) {
-
- var pluses = /\+/g;
-
- function raw(s) {
- return s;
- }
-
- function decoded(s) {
- return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
- }
-
- function unRfc2068(value) {
- if (value.indexOf('"') === 0) {
- // This is a quoted cookie as according to RFC2068, unescape
- value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
- }
- return value;
- }
-
- function fromJSON(value) {
- return config.json ? JSON.parse(value) : value;
- }
-
- var config = $.cookie = function (key, value, options) {
-
- // write
- if (value !== undefined) {
- options = $.extend({}, config.defaults, options);
-
- if (value === null) {
- options.expires = -1;
- }
-
- if (typeof options.expires === 'number') {
- var days = options.expires, t = options.expires = new Date();
- t.setDate(t.getDate() + days);
- }
-
- value = config.json ? JSON.stringify(value) : String(value);
-
- return (document.cookie = [
- encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
- options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
- options.path ? '; path=' + options.path : '',
- options.domain ? '; domain=' + options.domain : '',
- options.secure ? '; secure' : ''
- ].join(''));
- }
-
- // read
- var decode = config.raw ? raw : decoded;
- var cookies = document.cookie.split('; ');
- var result = key ? null : {};
- for (var i = 0, l = cookies.length; i < l; i++) {
- var parts = cookies[i].split('=');
- var name = decode(parts.shift());
- var cookie = decode(parts.join('='));
-
- if (key && key === name) {
- result = fromJSON(cookie);
- break;
- }
-
- if (!key) {
- result[name] = fromJSON(cookie);
- }
- }
-
- return result;
- };
-
- config.defaults = {};
-
- $.removeCookie = function (key, options) {
- if ($.cookie(key) !== null) {
- $.cookie(key, null, options);
- return true;
- }
- return false;
- };
-
-})(jQuery, document);
+++ /dev/null
-/*!
- * jQuery Form Plugin
- * version: 3.14 (30-JUL-2012)
- * @requires jQuery v1.3.2 or later
- *
- * Examples and documentation at: http://malsup.com/jquery/form/
- * Project repository: https://github.com/malsup/form
- * Dual licensed under the MIT and GPL licenses:
- * http://malsup.github.com/mit-license.txt
- * http://malsup.github.com/gpl-license-v2.txt
- */
-/*global ActiveXObject alert */
-;(function($) {
-"use strict";
-
-/*
- Usage Note:
- -----------
- Do not use both ajaxSubmit and ajaxForm on the same form. These
- functions are mutually exclusive. Use ajaxSubmit if you want
- to bind your own submit handler to the form. For example,
-
- $(document).ready(function() {
- $('#myForm').on('submit', function(e) {
- e.preventDefault(); // <-- important
- $(this).ajaxSubmit({
- target: '#output'
- });
- });
- });
-
- Use ajaxForm when you want the plugin to manage all the event binding
- for you. For example,
-
- $(document).ready(function() {
- $('#myForm').ajaxForm({
- target: '#output'
- });
- });
-
- You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
- form does not have to exist when you invoke ajaxForm:
-
- $('#myForm').ajaxForm({
- delegation: true,
- target: '#output'
- });
-
- When using ajaxForm, the ajaxSubmit function will be invoked for you
- at the appropriate time.
-*/
-
-/**
- * Feature detection
- */
-var feature = {};
-feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
-feature.formdata = window.FormData !== undefined;
-
-/**
- * ajaxSubmit() provides a mechanism for immediately submitting
- * an HTML form using AJAX.
- */
-$.fn.ajaxSubmit = function(options) {
- /*jshint scripturl:true */
-
- // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
- if (!this.length) {
- log('ajaxSubmit: skipping submit process - no element selected');
- return this;
- }
-
- var method, action, url, $form = this;
-
- if (typeof options == 'function') {
- options = { success: options };
- }
-
- method = this.attr('method');
- action = this.attr('action');
- url = (typeof action === 'string') ? $.trim(action) : '';
- url = url || window.location.href || '';
- if (url) {
- // clean url (don't include hash vaue)
- url = (url.match(/^([^#]+)/)||[])[1];
- }
-
- options = $.extend(true, {
- url: url,
- success: $.ajaxSettings.success,
- type: method || 'GET',
- iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
- }, options);
-
- // hook for manipulating the form data before it is extracted;
- // convenient for use with rich editors like tinyMCE or FCKEditor
- var veto = {};
- this.trigger('form-pre-serialize', [this, options, veto]);
- if (veto.veto) {
- log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
- return this;
- }
-
- // provide opportunity to alter form data before it is serialized
- if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
- log('ajaxSubmit: submit aborted via beforeSerialize callback');
- return this;
- }
-
- var traditional = options.traditional;
- if ( traditional === undefined ) {
- traditional = $.ajaxSettings.traditional;
- }
-
- var elements = [];
- var qx, a = this.formToArray(options.semantic, elements);
- if (options.data) {
- options.extraData = options.data;
- qx = $.param(options.data, traditional);
- }
-
- // give pre-submit callback an opportunity to abort the submit
- if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
- log('ajaxSubmit: submit aborted via beforeSubmit callback');
- return this;
- }
-
- // fire vetoable 'validate' event
- this.trigger('form-submit-validate', [a, this, options, veto]);
- if (veto.veto) {
- log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
- return this;
- }
-
- var q = $.param(a, traditional);
- if (qx) {
- q = ( q ? (q + '&' + qx) : qx );
- }
- if (options.type.toUpperCase() == 'GET') {
- options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
- options.data = null; // data is null for 'get'
- }
- else {
- options.data = q; // data is the query string for 'post'
- }
-
- var callbacks = [];
- if (options.resetForm) {
- callbacks.push(function() { $form.resetForm(); });
- }
- if (options.clearForm) {
- callbacks.push(function() { $form.clearForm(options.includeHidden); });
- }
-
- // perform a load on the target only if dataType is not provided
- if (!options.dataType && options.target) {
- var oldSuccess = options.success || function(){};
- callbacks.push(function(data) {
- var fn = options.replaceTarget ? 'replaceWith' : 'html';
- $(options.target)[fn](data).each(oldSuccess, arguments);
- });
- }
- else if (options.success) {
- callbacks.push(options.success);
- }
-
- options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
- var context = options.context || this ; // jQuery 1.4+ supports scope context
- for (var i=0, max=callbacks.length; i < max; i++) {
- callbacks[i].apply(context, [data, status, xhr || $form, $form]);
- }
- };
-
- // are there files to upload?
- var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
- var hasFileInputs = fileInputs.length > 0;
- var mp = 'multipart/form-data';
- var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
-
- var fileAPI = feature.fileapi && feature.formdata;
- log("fileAPI :" + fileAPI);
- var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
-
- // options.iframe allows user to force iframe mode
- // 06-NOV-09: now defaulting to iframe mode if file input is detected
- if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
- // hack to fix Safari hang (thanks to Tim Molendijk for this)
- // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
- if (options.closeKeepAlive) {
- $.get(options.closeKeepAlive, function() {
- fileUploadIframe(a);
- });
- }
- else {
- fileUploadIframe(a);
- }
- }
- else if ((hasFileInputs || multipart) && fileAPI) {
- fileUploadXhr(a);
- }
- else {
- $.ajax(options);
- }
-
- // clear element array
- for (var k=0; k < elements.length; k++)
- elements[k] = null;
-
- // fire 'notify' event
- this.trigger('form-submit-notify', [this, options]);
- return this;
-
- // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
- function fileUploadXhr(a) {
- var formdata = new FormData();
-
- for (var i=0; i < a.length; i++) {
- formdata.append(a[i].name, a[i].value);
- }
-
- if (options.extraData) {
- for (var p in options.extraData)
- if (options.extraData.hasOwnProperty(p))
- formdata.append(p, options.extraData[p]);
- }
-
- options.data = null;
-
- var s = $.extend(true, {}, $.ajaxSettings, options, {
- contentType: false,
- processData: false,
- cache: false,
- type: 'POST'
- });
-
- if (options.uploadProgress) {
- // workaround because jqXHR does not expose upload property
- s.xhr = function() {
- var xhr = jQuery.ajaxSettings.xhr();
- if (xhr.upload) {
- xhr.upload.onprogress = function(event) {
- var percent = 0;
- var position = event.loaded || event.position; /*event.position is deprecated*/
- var total = event.total;
- if (event.lengthComputable) {
- percent = Math.ceil(position / total * 100);
- }
- options.uploadProgress(event, position, total, percent);
- };
- }
- return xhr;
- };
- }
-
- s.data = null;
- var beforeSend = s.beforeSend;
- s.beforeSend = function(xhr, o) {
- o.data = formdata;
- if(beforeSend)
- beforeSend.call(this, xhr, o);
- };
- $.ajax(s);
- }
-
- // private function for handling file uploads (hat tip to YAHOO!)
- function fileUploadIframe(a) {
- var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
- var useProp = !!$.fn.prop;
-
- if ($(':input[name=submit],:input[id=submit]', form).length) {
- // if there is an input with a name or id of 'submit' then we won't be
- // able to invoke the submit fn on the form (at least not x-browser)
- alert('Error: Form elements must not have name or id of "submit".');
- return;
- }
-
- if (a) {
- // ensure that every serialized input is still enabled
- for (i=0; i < elements.length; i++) {
- el = $(elements[i]);
- if ( useProp )
- el.prop('disabled', false);
- else
- el.removeAttr('disabled');
- }
- }
-
- s = $.extend(true, {}, $.ajaxSettings, options);
- s.context = s.context || s;
- id = 'jqFormIO' + (new Date().getTime());
- if (s.iframeTarget) {
- $io = $(s.iframeTarget);
- n = $io.attr('name');
- if (!n)
- $io.attr('name', id);
- else
- id = n;
- }
- else {
- $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
- $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
- }
- io = $io[0];
-
-
- xhr = { // mock object
- aborted: 0,
- responseText: null,
- responseXML: null,
- status: 0,
- statusText: 'n/a',
- getAllResponseHeaders: function() {},
- getResponseHeader: function() {},
- setRequestHeader: function() {},
- abort: function(status) {
- var e = (status === 'timeout' ? 'timeout' : 'aborted');
- log('aborting upload... ' + e);
- this.aborted = 1;
- // #214
- if (io.contentWindow.document.execCommand) {
- try { // #214
- io.contentWindow.document.execCommand('Stop');
- } catch(ignore) {}
- }
- $io.attr('src', s.iframeSrc); // abort op in progress
- xhr.error = e;
- if (s.error)
- s.error.call(s.context, xhr, e, status);
- if (g)
- $.event.trigger("ajaxError", [xhr, s, e]);
- if (s.complete)
- s.complete.call(s.context, xhr, e);
- }
- };
-
- g = s.global;
- // trigger ajax global events so that activity/block indicators work like normal
- if (g && 0 === $.active++) {
- $.event.trigger("ajaxStart");
- }
- if (g) {
- $.event.trigger("ajaxSend", [xhr, s]);
- }
-
- if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
- if (s.global) {
- $.active--;
- }
- return;
- }
- if (xhr.aborted) {
- return;
- }
-
- // add submitting element to data if we know it
- sub = form.clk;
- if (sub) {
- n = sub.name;
- if (n && !sub.disabled) {
- s.extraData = s.extraData || {};
- s.extraData[n] = sub.value;
- if (sub.type == "image") {
- s.extraData[n+'.x'] = form.clk_x;
- s.extraData[n+'.y'] = form.clk_y;
- }
- }
- }
-
- var CLIENT_TIMEOUT_ABORT = 1;
- var SERVER_ABORT = 2;
-
- function getDoc(frame) {
- var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
- return doc;
- }
-
- // Rails CSRF hack (thanks to Yvan Barthelemy)
- var csrf_token = $('meta[name=csrf-token]').attr('content');
- var csrf_param = $('meta[name=csrf-param]').attr('content');
- if (csrf_param && csrf_token) {
- s.extraData = s.extraData || {};
- s.extraData[csrf_param] = csrf_token;
- }
-
- // take a breath so that pending repaints get some cpu time before the upload starts
- function doSubmit() {
- // make sure form attrs are set
- var t = $form.attr('target'), a = $form.attr('action');
-
- // update form attrs in IE friendly way
- form.setAttribute('target',id);
- if (!method) {
- form.setAttribute('method', 'POST');
- }
- if (a != s.url) {
- form.setAttribute('action', s.url);
- }
-
- // ie borks in some cases when setting encoding
- if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
- $form.attr({
- encoding: 'multipart/form-data',
- enctype: 'multipart/form-data'
- });
- }
-
- // support timout
- if (s.timeout) {
- timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
- }
-
- // look for server aborts
- function checkState() {
- try {
- var state = getDoc(io).readyState;
- log('state = ' + state);
- if (state && state.toLowerCase() == 'uninitialized')
- setTimeout(checkState,50);
- }
- catch(e) {
- log('Server abort: ' , e, ' (', e.name, ')');
- cb(SERVER_ABORT);
- if (timeoutHandle)
- clearTimeout(timeoutHandle);
- timeoutHandle = undefined;
- }
- }
-
- // add "extra" data to form if provided in options
- var extraInputs = [];
- try {
- if (s.extraData) {
- for (var n in s.extraData) {
- if (s.extraData.hasOwnProperty(n)) {
- // if using the $.param format that allows for multiple values with the same name
- if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
- extraInputs.push(
- $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
- .appendTo(form)[0]);
- } else {
- extraInputs.push(
- $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
- .appendTo(form)[0]);
- }
- }
- }
- }
-
- if (!s.iframeTarget) {
- // add iframe to doc and submit the form
- $io.appendTo('body');
- if (io.attachEvent)
- io.attachEvent('onload', cb);
- else
- io.addEventListener('load', cb, false);
- }
- setTimeout(checkState,15);
- form.submit();
- }
- finally {
- // reset attrs and remove "extra" input elements
- form.setAttribute('action',a);
- if(t) {
- form.setAttribute('target', t);
- } else {
- $form.removeAttr('target');
- }
- $(extraInputs).remove();
- }
- }
-
- if (s.forceSync) {
- doSubmit();
- }
- else {
- setTimeout(doSubmit, 10); // this lets dom updates render
- }
-
- var data, doc, domCheckCount = 50, callbackProcessed;
-
- function cb(e) {
- if (xhr.aborted || callbackProcessed) {
- return;
- }
- try {
- doc = getDoc(io);
- }
- catch(ex) {
- log('cannot access response document: ', ex);
- e = SERVER_ABORT;
- }
- if (e === CLIENT_TIMEOUT_ABORT && xhr) {
- xhr.abort('timeout');
- return;
- }
- else if (e == SERVER_ABORT && xhr) {
- xhr.abort('server abort');
- return;
- }
-
- if (!doc || doc.location.href == s.iframeSrc) {
- // response not received yet
- if (!timedOut)
- return;
- }
- if (io.detachEvent)
- io.detachEvent('onload', cb);
- else
- io.removeEventListener('load', cb, false);
-
- var status = 'success', errMsg;
- try {
- if (timedOut) {
- throw 'timeout';
- }
-
- var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
- log('isXml='+isXml);
- if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
- if (--domCheckCount) {
- // in some browsers (Opera) the iframe DOM is not always traversable when
- // the onload callback fires, so we loop a bit to accommodate
- log('requeing onLoad callback, DOM not available');
- setTimeout(cb, 250);
- return;
- }
- // let this fall through because server response could be an empty document
- //log('Could not access iframe DOM after mutiple tries.');
- //throw 'DOMException: not available';
- }
-
- //log('response detected');
- var docRoot = doc.body ? doc.body : doc.documentElement;
- xhr.responseText = docRoot ? docRoot.innerHTML : null;
- xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
- if (isXml)
- s.dataType = 'xml';
- xhr.getResponseHeader = function(header){
- var headers = {'content-type': s.dataType};
- return headers[header];
- };
- // support for XHR 'status' & 'statusText' emulation :
- if (docRoot) {
- xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
- xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
- }
-
- var dt = (s.dataType || '').toLowerCase();
- var scr = /(json|script|text)/.test(dt);
- if (scr || s.textarea) {
- // see if user embedded response in textarea
- var ta = doc.getElementsByTagName('textarea')[0];
- if (ta) {
- xhr.responseText = ta.value;
- // support for XHR 'status' & 'statusText' emulation :
- xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
- xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
- }
- else if (scr) {
- // account for browsers injecting pre around json response
- var pre = doc.getElementsByTagName('pre')[0];
- var b = doc.getElementsByTagName('body')[0];
- if (pre) {
- xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
- }
- else if (b) {
- xhr.responseText = b.textContent ? b.textContent : b.innerText;
- }
- }
- }
- else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
- xhr.responseXML = toXml(xhr.responseText);
- }
-
- try {
- data = httpData(xhr, dt, s);
- }
- catch (e) {
- status = 'parsererror';
- xhr.error = errMsg = (e || status);
- }
- }
- catch (e) {
- log('error caught: ',e);
- status = 'error';
- xhr.error = errMsg = (e || status);
- }
-
- if (xhr.aborted) {
- log('upload aborted');
- status = null;
- }
-
- if (xhr.status) { // we've set xhr.status
- status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
- }
-
- // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
- if (status === 'success') {
- if (s.success)
- s.success.call(s.context, data, 'success', xhr);
- if (g)
- $.event.trigger("ajaxSuccess", [xhr, s]);
- }
- else if (status) {
- if (errMsg === undefined)
- errMsg = xhr.statusText;
- if (s.error)
- s.error.call(s.context, xhr, status, errMsg);
- if (g)
- $.event.trigger("ajaxError", [xhr, s, errMsg]);
- }
-
- if (g)
- $.event.trigger("ajaxComplete", [xhr, s]);
-
- if (g && ! --$.active) {
- $.event.trigger("ajaxStop");
- }
-
- if (s.complete)
- s.complete.call(s.context, xhr, status);
-
- callbackProcessed = true;
- if (s.timeout)
- clearTimeout(timeoutHandle);
-
- // clean up
- setTimeout(function() {
- if (!s.iframeTarget)
- $io.remove();
- xhr.responseXML = null;
- }, 100);
- }
-
- var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
- if (window.ActiveXObject) {
- doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- doc.loadXML(s);
- }
- else {
- doc = (new DOMParser()).parseFromString(s, 'text/xml');
- }
- return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
- };
- var parseJSON = $.parseJSON || function(s) {
- /*jslint evil:true */
- return window['eval']('(' + s + ')');
- };
-
- var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
-
- var ct = xhr.getResponseHeader('content-type') || '',
- xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
- data = xml ? xhr.responseXML : xhr.responseText;
-
- if (xml && data.documentElement.nodeName === 'parsererror') {
- if ($.error)
- $.error('parsererror');
- }
- if (s && s.dataFilter) {
- data = s.dataFilter(data, type);
- }
- if (typeof data === 'string') {
- if (type === 'json' || !type && ct.indexOf('json') >= 0) {
- data = parseJSON(data);
- } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
- $.globalEval(data);
- }
- }
- return data;
- };
- }
-};
-
-/**
- * ajaxForm() provides a mechanism for fully automating form submission.
- *
- * The advantages of using this method instead of ajaxSubmit() are:
- *
- * 1: This method will include coordinates for <input type="image" /> elements (if the element
- * is used to submit the form).
- * 2. This method will include the submit element's name/value data (for the element that was
- * used to submit the form).
- * 3. This method binds the submit() method to the form for you.
- *
- * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
- * passes the options argument along after properly binding events for submit elements and
- * the form itself.
- */
-$.fn.ajaxForm = function(options) {
- options = options || {};
- options.delegation = options.delegation && $.isFunction($.fn.on);
-
- // in jQuery 1.3+ we can fix mistakes with the ready state
- if (!options.delegation && this.length === 0) {
- var o = { s: this.selector, c: this.context };
- if (!$.isReady && o.s) {
- log('DOM not ready, queuing ajaxForm');
- $(function() {
- $(o.s,o.c).ajaxForm(options);
- });
- return this;
- }
- // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
- log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
- return this;
- }
-
- if ( options.delegation ) {
- $(document)
- .off('submit.form-plugin', this.selector, doAjaxSubmit)
- .off('click.form-plugin', this.selector, captureSubmittingElement)
- .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
- .on('click.form-plugin', this.selector, options, captureSubmittingElement);
- return this;
- }
-
- return this.ajaxFormUnbind()
- .bind('submit.form-plugin', options, doAjaxSubmit)
- .bind('click.form-plugin', options, captureSubmittingElement);
-};
-
-// private event handlers
-function doAjaxSubmit(e) {
- /*jshint validthis:true */
- var options = e.data;
- if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
- e.preventDefault();
- $(this).ajaxSubmit(options);
- }
-}
-
-function captureSubmittingElement(e) {
- /*jshint validthis:true */
- var target = e.target;
- var $el = $(target);
- if (!($el.is(":submit,input:image"))) {
- // is this a child element of the submit el? (ex: a span within a button)
- var t = $el.closest(':submit');
- if (t.length === 0) {
- return;
- }
- target = t[0];
- }
- var form = this;
- form.clk = target;
- if (target.type == 'image') {
- if (e.offsetX !== undefined) {
- form.clk_x = e.offsetX;
- form.clk_y = e.offsetY;
- } else if (typeof $.fn.offset == 'function') {
- var offset = $el.offset();
- form.clk_x = e.pageX - offset.left;
- form.clk_y = e.pageY - offset.top;
- } else {
- form.clk_x = e.pageX - target.offsetLeft;
- form.clk_y = e.pageY - target.offsetTop;
- }
- }
- // clear form vars
- setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
-}
-
-
-// ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
-$.fn.ajaxFormUnbind = function() {
- return this.unbind('submit.form-plugin click.form-plugin');
-};
-
-/**
- * formToArray() gathers form element data into an array of objects that can
- * be passed to any of the following ajax functions: $.get, $.post, or load.
- * Each object in the array has both a 'name' and 'value' property. An example of
- * an array for a simple login form might be:
- *
- * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
- *
- * It is this array that is passed to pre-submit callback functions provided to the
- * ajaxSubmit() and ajaxForm() methods.
- */
-$.fn.formToArray = function(semantic, elements) {
- var a = [];
- if (this.length === 0) {
- return a;
- }
-
- var form = this[0];
- var els = semantic ? form.getElementsByTagName('*') : form.elements;
- if (!els) {
- return a;
- }
-
- var i,j,n,v,el,max,jmax;
- for(i=0, max=els.length; i < max; i++) {
- el = els[i];
- n = el.name;
- if (!n) {
- continue;
- }
-
- if (semantic && form.clk && el.type == "image") {
- // handle image inputs on the fly when semantic == true
- if(!el.disabled && form.clk == el) {
- a.push({name: n, value: $(el).val(), type: el.type });
- a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
- }
- continue;
- }
-
- v = $.fieldValue(el, true);
- if (v && v.constructor == Array) {
- if (elements)
- elements.push(el);
- for(j=0, jmax=v.length; j < jmax; j++) {
- a.push({name: n, value: v[j]});
- }
- }
- else if (feature.fileapi && el.type == 'file' && !el.disabled) {
- if (elements)
- elements.push(el);
- var files = el.files;
- if (files.length) {
- for (j=0; j < files.length; j++) {
- a.push({name: n, value: files[j], type: el.type});
- }
- }
- else {
- // #180
- a.push({ name: n, value: '', type: el.type });
- }
- }
- else if (v !== null && typeof v != 'undefined') {
- if (elements)
- elements.push(el);
- a.push({name: n, value: v, type: el.type, required: el.required});
- }
- }
-
- if (!semantic && form.clk) {
- // input type=='image' are not found in elements array! handle it here
- var $input = $(form.clk), input = $input[0];
- n = input.name;
- if (n && !input.disabled && input.type == 'image') {
- a.push({name: n, value: $input.val()});
- a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
- }
- }
- return a;
-};
-
-/**
- * Serializes form data into a 'submittable' string. This method will return a string
- * in the format: name1=value1&name2=value2
- */
-$.fn.formSerialize = function(semantic) {
- //hand off to jQuery.param for proper encoding
- return $.param(this.formToArray(semantic));
-};
-
-/**
- * Serializes all field elements in the jQuery object into a query string.
- * This method will return a string in the format: name1=value1&name2=value2
- */
-$.fn.fieldSerialize = function(successful) {
- var a = [];
- this.each(function() {
- var n = this.name;
- if (!n) {
- return;
- }
- var v = $.fieldValue(this, successful);
- if (v && v.constructor == Array) {
- for (var i=0,max=v.length; i < max; i++) {
- a.push({name: n, value: v[i]});
- }
- }
- else if (v !== null && typeof v != 'undefined') {
- a.push({name: this.name, value: v});
- }
- });
- //hand off to jQuery.param for proper encoding
- return $.param(a);
-};
-
-/**
- * Returns the value(s) of the element in the matched set. For example, consider the following form:
- *
- * <form><fieldset>
- * <input name="A" type="text" />
- * <input name="A" type="text" />
- * <input name="B" type="checkbox" value="B1" />
- * <input name="B" type="checkbox" value="B2"/>
- * <input name="C" type="radio" value="C1" />
- * <input name="C" type="radio" value="C2" />
- * </fieldset></form>
- *
- * var v = $(':text').fieldValue();
- * // if no values are entered into the text inputs
- * v == ['','']
- * // if values entered into the text inputs are 'foo' and 'bar'
- * v == ['foo','bar']
- *
- * var v = $(':checkbox').fieldValue();
- * // if neither checkbox is checked
- * v === undefined
- * // if both checkboxes are checked
- * v == ['B1', 'B2']
- *
- * var v = $(':radio').fieldValue();
- * // if neither radio is checked
- * v === undefined
- * // if first radio is checked
- * v == ['C1']
- *
- * The successful argument controls whether or not the field element must be 'successful'
- * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
- * The default value of the successful argument is true. If this value is false the value(s)
- * for each element is returned.
- *
- * Note: This method *always* returns an array. If no valid value can be determined the
- * array will be empty, otherwise it will contain one or more values.
- */
-$.fn.fieldValue = function(successful) {
- for (var val=[], i=0, max=this.length; i < max; i++) {
- var el = this[i];
- var v = $.fieldValue(el, successful);
- if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
- continue;
- }
- if (v.constructor == Array)
- $.merge(val, v);
- else
- val.push(v);
- }
- return val;
-};
-
-/**
- * Returns the value of the field element.
- */
-$.fieldValue = function(el, successful) {
- var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
- if (successful === undefined) {
- successful = true;
- }
-
- if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
- (t == 'checkbox' || t == 'radio') && !el.checked ||
- (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
- tag == 'select' && el.selectedIndex == -1)) {
- return null;
- }
-
- if (tag == 'select') {
- var index = el.selectedIndex;
- if (index < 0) {
- return null;
- }
- var a = [], ops = el.options;
- var one = (t == 'select-one');
- var max = (one ? index+1 : ops.length);
- for(var i=(one ? index : 0); i < max; i++) {
- var op = ops[i];
- if (op.selected) {
- var v = op.value;
- if (!v) { // extra pain for IE...
- v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
- }
- if (one) {
- return v;
- }
- a.push(v);
- }
- }
- return a;
- }
- return $(el).val();
-};
-
-/**
- * Clears the form data. Takes the following actions on the form's input fields:
- * - input text fields will have their 'value' property set to the empty string
- * - select elements will have their 'selectedIndex' property set to -1
- * - checkbox and radio inputs will have their 'checked' property set to false
- * - inputs of type submit, button, reset, and hidden will *not* be effected
- * - button elements will *not* be effected
- */
-$.fn.clearForm = function(includeHidden) {
- return this.each(function() {
- $('input,select,textarea', this).clearFields(includeHidden);
- });
-};
-
-/**
- * Clears the selected form elements.
- */
-$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
- var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
- return this.each(function() {
- var t = this.type, tag = this.tagName.toLowerCase();
- if (re.test(t) || tag == 'textarea') {
- this.value = '';
- }
- else if (t == 'checkbox' || t == 'radio') {
- this.checked = false;
- }
- else if (tag == 'select') {
- this.selectedIndex = -1;
- }
- else if (includeHidden) {
- // includeHidden can be the value true, or it can be a selector string
- // indicating a special test; for example:
- // $('#myForm').clearForm('.special:hidden')
- // the above would clean hidden inputs that have the class of 'special'
- if ( (includeHidden === true && /hidden/.test(t)) ||
- (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
- this.value = '';
- }
- });
-};
-
-/**
- * Resets the form data. Causes all form elements to be reset to their original value.
- */
-$.fn.resetForm = function() {
- return this.each(function() {
- // guard against an input with the name of 'reset'
- // note that IE reports the reset function as an 'object'
- if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
- this.reset();
- }
- });
-};
-
-/**
- * Enables or disables any matching elements.
- */
-$.fn.enable = function(b) {
- if (b === undefined) {
- b = true;
- }
- return this.each(function() {
- this.disabled = !b;
- });
-};
-
-/**
- * Checks/unchecks any matching checkboxes or radio buttons and
- * selects/deselects and matching option elements.
- */
-$.fn.selected = function(select) {
- if (select === undefined) {
- select = true;
- }
- return this.each(function() {
- var t = this.type;
- if (t == 'checkbox' || t == 'radio') {
- this.checked = select;
- }
- else if (this.tagName.toLowerCase() == 'option') {
- var $sel = $(this).parent('select');
- if (select && $sel[0] && $sel[0].type == 'select-one') {
- // deselect all other options
- $sel.find('option').selected(false);
- }
- this.selected = select;
- }
- });
-};
-
-// expose debug var
-$.fn.ajaxSubmit.debug = false;
-
-// helper fn for console logging
-function log() {
- if (!$.fn.ajaxSubmit.debug)
- return;
- var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
- if (window.console && window.console.log) {
- window.console.log(msg);
- }
- else if (window.opera && window.opera.postError) {
- window.opera.postError(msg);
- }
-}
-
-})(jQuery);
+++ /dev/null
-/**
- * jQuery fullscreen plugin v2.0.0-git (9f8f97d127)
- * https://github.com/theopolisme/jquery-fullscreen
- *
- * Copyright (c) 2013 Theopolisme <theopolismewiki@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
-( function ( $ ) {
- var setupFullscreen,
- fsClass = 'jq-fullscreened';
-
- /**
- * On fullscreenchange, trigger a jq-fullscreen-change event
- * The event is given an object, which contains the fullscreened DOM element (element), if any
- * and a boolean value (fullscreen) indicating if we've entered or exited fullscreen mode
- * Also remove the 'fullscreened' class from elements that are no longer fullscreen
- */
- function handleFullscreenChange () {
- var fullscreenElement = document.fullscreenElement ||
- document.mozFullScreenElement ||
- document.webkitFullscreenElement ||
- document.msFullscreenElement;
-
- if ( !fullscreenElement ) {
- $( '.' + fsClass ).data( 'isFullscreened', false ).removeClass( fsClass );
- }
-
- $( document ).trigger( $.Event( 'jq-fullscreen-change', { element: fullscreenElement, fullscreen: !!fullscreenElement } ) );
- }
-
- /**
- * Enters full screen with the "this" element in focus.
- * Check the .data( 'isFullscreened' ) of the return value to check
- * success or failure, if you're into that sort of thing.
- * @chainable
- * @return {jQuery}
- */
- function enterFullscreen () {
- var element = this.get(0),
- $element = this.first();
- if ( element ) {
- if ( element.requestFullscreen ) {
- element.requestFullscreen();
- } else if ( element.mozRequestFullScreen ) {
- element.mozRequestFullScreen();
- } else if ( element.webkitRequestFullscreen ) {
- element.webkitRequestFullscreen();
- } else if ( element.msRequestFullscreen ) {
- element.msRequestFullscreen();
- } else {
- // Unable to make fullscreen
- $element.data( 'isFullscreened', false );
- return this;
- }
- // Add the fullscreen class and data attribute to `element`
- $element.addClass( fsClass ).data( 'isFullscreened', true );
- return this;
- } else {
- $element.data( 'isFullscreened', false );
- return this;
- }
- }
-
- /**
- * Brings the "this" element out of fullscreen.
- * Check the .data( 'isFullscreened' ) of the return value to check
- * success or failure, if you're into that sort of thing.
- * @chainable
- * @return {jQuery}
- */
- function exitFullscreen () {
- var fullscreenElement = ( document.fullscreenElement ||
- document.mozFullScreenElement ||
- document.webkitFullscreenElement ||
- document.msFullscreenElement );
-
- // Ensure that we only exit fullscreen if exitFullscreen() is being called on the same element that is currently fullscreen
- if ( fullscreenElement && this.get(0) === fullscreenElement ) {
- if ( document.exitFullscreen ) {
- document.exitFullscreen();
- } else if ( document.mozCancelFullScreen ) {
- document.mozCancelFullScreen();
- } else if ( document.webkitCancelFullScreen ) {
- document.webkitCancelFullScreen();
- } else if ( document.msExitFullscreen ) {
- document.msExitFullscreen();
- } else {
- // Unable to cancel fullscreen mode
- return this;
- }
- // We don't need to remove the fullscreen class here,
- // because it will be removed in handleFullscreenChange.
- // But we should change the data on the element so the
- // caller can check for success.
- this.first().data( 'isFullscreened', false );
- }
-
- return this;
- }
-
- /**
- * Set up fullscreen handling and install necessary event handlers.
- * Return false if fullscreen is not supported.
- */
- setupFullscreen = function () {
- if ( $.support.fullscreen ) {
- // When the fullscreen mode is changed, trigger the
- // fullscreen events (and when exiting,
- // also remove the fullscreen class)
- $( document ).on( 'fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange', handleFullscreenChange);
- // Convenience wrapper so that one only needs to listen for
- // 'fullscreenerror', not all of the prefixed versions
- $( document ).on( 'webkitfullscreenerror mozfullscreenerror MSFullscreenError', function () {
- $( document ).trigger( $.Event( 'fullscreenerror' ) );
- } );
- // Fullscreen has been set up, so always return true
- setupFullscreen = function () { return true; };
- return true;
- } else {
- // Always return false from now on, since fullscreen is not supported
- setupFullscreen = function () { return false; };
- return false;
- }
- };
-
- /**
- * Set up fullscreen handling if necessary, then make the first element
- * matching the given selector fullscreen
- * @chainable
- * @return {jQuery}
- */
- $.fn.enterFullscreen = function () {
- if ( setupFullscreen() ) {
- $.fn.enterFullscreen = enterFullscreen;
- return this.enterFullscreen();
- } else {
- $.fn.enterFullscreen = function () { return this; };
- return this;
- }
- };
-
- /**
- * Set up fullscreen handling if necessary, then cancel fullscreen mode
- * for the first element matching the given selector.
- * @chainable
- * @return {jQuery}
- */
- $.fn.exitFullscreen = function () {
- if ( setupFullscreen() ) {
- $.fn.exitFullscreen = exitFullscreen;
- return this.exitFullscreen();
- } else {
- $.fn.exitFullscreen = function () { return this; };
- return this;
- }
- };
-
- $.support.fullscreen = document.fullscreenEnabled ||
- document.webkitFullscreenEnabled ||
- document.mozFullScreenEnabled ||
- document.msFullscreenEnabled;
-}( jQuery ) );
+++ /dev/null
-/**
-* hoverIntent is similar to jQuery's built-in "hover" function except that
-* instead of firing the onMouseOver event immediately, hoverIntent checks
-* to see if the user's mouse has slowed down (beneath the sensitivity
-* threshold) before firing the onMouseOver event.
-*
-* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
-* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
-*
-* hoverIntent is currently available for use in all personal or commercial
-* projects under both MIT and GPL licenses. This means that you can choose
-* the license that best suits your project, and use it accordingly.
-*
-* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
-* $("ul li").hoverIntent( showNav , hideNav );
-*
-* // advanced usage receives configuration object only
-* $("ul li").hoverIntent({
-* sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
-* interval: 100, // number = milliseconds of polling interval
-* over: showNav, // function = onMouseOver callback (required)
-* timeout: 0, // number = milliseconds delay before onMouseOut function call
-* out: hideNav // function = onMouseOut callback (required)
-* });
-*
-* @param f onMouseOver function || An object with configuration options
-* @param g onMouseOut function || Nothing (use configuration options object)
-* @author Brian Cherne <brian@cherne.net>
-*/
-(function($) {
- $.fn.hoverIntent = function(f,g) {
- // default configuration options
- var cfg = {
- sensitivity: 7,
- interval: 100,
- timeout: 0
- };
- // override configuration options with user supplied object
- cfg = $.extend(cfg, g ? { over: f, out: g } : f );
-
- // instantiate variables
- // cX, cY = current X and Y position of mouse, updated by mousemove event
- // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
- var cX, cY, pX, pY;
-
- // A private function for getting mouse position
- var track = function(ev) {
- cX = ev.pageX;
- cY = ev.pageY;
- };
-
- // A private function for comparing current and previous mouse position
- var compare = function(ev,ob) {
- ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
- // compare mouse positions to see if they've crossed the threshold
- if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
- $(ob).unbind("mousemove",track);
- // set hoverIntent state to true (so mouseOut can be called)
- ob.hoverIntent_s = 1;
- return cfg.over.apply(ob,[ev]);
- } else {
- // set previous coordinates for next time
- pX = cX; pY = cY;
- // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
- ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
- }
- };
-
- // A private function for delaying the mouseOut function
- var delay = function(ev,ob) {
- ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
- ob.hoverIntent_s = 0;
- return cfg.out.apply(ob,[ev]);
- };
-
- // A private function for handling mouse 'hovering'
- var handleHover = function(e) {
- // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
- var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
- while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
- if ( p == this ) { return false; }
-
- // copy objects to be passed into t (required for event object to be passed in IE)
- var ev = $.extend({},e);
- var ob = this;
-
- // cancel hoverIntent timer if it exists
- if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }
-
- // else e.type == "onmouseover"
- if (e.type == "mouseover") {
- // set "previous" X and Y position based on initial entry point
- pX = ev.pageX; pY = ev.pageY;
- // update "current" X and Y position based on mousemove
- $(ob).bind("mousemove",track);
- // start polling interval (self-calling timeout) to compare mouse coordinates over time
- if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}
-
- // else e.type == "onmouseout"
- } else {
- // unbind expensive mousemove event
- $(ob).unbind("mousemove",track);
- // if hoverIntent state is true, then call the mouseOut function after the specified delay
- if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
- }
- };
-
- // bind the function to the two event listeners
- return this.mouseover(handleHover).mouseout(handleHover);
- };
-})(jQuery);
\ No newline at end of file
+++ /dev/null
-/*
- * ----------------------------- JSTORAGE -------------------------------------
- * Simple local storage wrapper to save data on the browser side, supporting
- * all major browsers - IE6+, Firefox2+, Safari4+, Chrome4+ and Opera 10.5+
- *
- * Author: Andris Reinman, andris.reinman@gmail.com
- * Project homepage: www.jstorage.info
- *
- * Licensed under Unlicense:
- *
- * This is free and unencumbered software released into the public domain.
- *
- * Anyone is free to copy, modify, publish, use, compile, sell, or
- * distribute this software, either in source code form or as a compiled
- * binary, for any purpose, commercial or non-commercial, and by any
- * means.
- *
- * In jurisdictions that recognize copyright laws, the author or authors
- * of this software dedicate any and all copyright interest in the
- * software to the public domain. We make this dedication for the benefit
- * of the public at large and to the detriment of our heirs and
- * successors. We intend this dedication to be an overt act of
- * relinquishment in perpetuity of all present and future rights to this
- * software under copyright law.
- *
- * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
- * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
- * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
- * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
- * OTHER DEALINGS IN THE SOFTWARE.
- *
- * For more information, please refer to <http://unlicense.org/>
- */
-
-/* global ActiveXObject: false */
-/* jshint browser: true */
-
-(function() {
- 'use strict';
-
- var
- /* jStorage version */
- JSTORAGE_VERSION = '0.4.12',
-
- /* detect a dollar object or create one if not found */
- $ = window.jQuery || window.$ || (window.$ = {}),
-
- /* check for a JSON handling support */
- JSON = {
- parse: window.JSON && (window.JSON.parse || window.JSON.decode) ||
- String.prototype.evalJSON && function(str) {
- return String(str).evalJSON();
- } ||
- $.parseJSON ||
- $.evalJSON,
- stringify: Object.toJSON ||
- window.JSON && (window.JSON.stringify || window.JSON.encode) ||
- $.toJSON
- };
-
- // Break if no JSON support was found
- if (typeof JSON.parse !== 'function' || typeof JSON.stringify !== 'function') {
- throw new Error('No JSON support found, include //cdnjs.cloudflare.com/ajax/libs/json2/20110223/json2.js to page');
- }
-
- var
- /* This is the object, that holds the cached values */
- _storage = {
- __jstorage_meta: {
- CRC32: {}
- }
- },
-
- /* Actual browser storage (localStorage or globalStorage['domain']) */
- _storage_service = {
- jStorage: '{}'
- },
-
- /* DOM element for older IE versions, holds userData behavior */
- _storage_elm = null,
-
- /* How much space does the storage take */
- _storage_size = 0,
-
- /* which backend is currently used */
- _backend = false,
-
- /* onchange observers */
- _observers = {},
-
- /* timeout to wait after onchange event */
- _observer_timeout = false,
-
- /* last update time */
- _observer_update = 0,
-
- /* pubsub observers */
- _pubsub_observers = {},
-
- /* skip published items older than current timestamp */
- _pubsub_last = +new Date(),
-
- /* Next check for TTL */
- _ttl_timeout,
-
- /**
- * XML encoding and decoding as XML nodes can't be JSON'ized
- * XML nodes are encoded and decoded if the node is the value to be saved
- * but not if it's as a property of another object
- * Eg. -
- * $.jStorage.set('key', xmlNode); // IS OK
- * $.jStorage.set('key', {xml: xmlNode}); // NOT OK
- */
- _XMLService = {
-
- /**
- * Validates a XML node to be XML
- * based on jQuery.isXML function
- */
- isXML: function(elm) {
- var documentElement = (elm ? elm.ownerDocument || elm : 0).documentElement;
- return documentElement ? documentElement.nodeName !== 'HTML' : false;
- },
-
- /**
- * Encodes a XML node to string
- * based on http://www.mercurytide.co.uk/news/article/issues-when-working-ajax/
- */
- encode: function(xmlNode) {
- if (!this.isXML(xmlNode)) {
- return false;
- }
- try { // Mozilla, Webkit, Opera
- return new XMLSerializer().serializeToString(xmlNode);
- } catch (E1) {
- try { // IE
- return xmlNode.xml;
- } catch (E2) {}
- }
- return false;
- },
-
- /**
- * Decodes a XML node from string
- * loosely based on http://outwestmedia.com/jquery-plugins/xmldom/
- */
- decode: function(xmlString) {
- var dom_parser = ('DOMParser' in window && (new DOMParser()).parseFromString) ||
- (window.ActiveXObject && function(_xmlString) {
- var xml_doc = new ActiveXObject('Microsoft.XMLDOM');
- xml_doc.async = 'false';
- xml_doc.loadXML(_xmlString);
- return xml_doc;
- }),
- resultXML;
- if (!dom_parser) {
- return false;
- }
- resultXML = dom_parser.call('DOMParser' in window && (new DOMParser()) || window, xmlString, 'text/xml');
- return this.isXML(resultXML) ? resultXML : false;
- }
- };
-
-
- ////////////////////////// PRIVATE METHODS ////////////////////////
-
- /**
- * Initialization function. Detects if the browser supports DOM Storage
- * or userData behavior and behaves accordingly.
- */
- function _init() {
- /* Check if browser supports localStorage */
- var localStorageReallyWorks = false;
- if ('localStorage' in window) {
- try {
- window.localStorage.setItem('_tmptest', 'tmpval');
- localStorageReallyWorks = true;
- window.localStorage.removeItem('_tmptest');
- } catch (BogusQuotaExceededErrorOnIos5) {
- // Thanks be to iOS5 Private Browsing mode which throws
- // QUOTA_EXCEEDED_ERRROR DOM Exception 22.
- }
- }
-
- if (localStorageReallyWorks) {
- try {
- if (window.localStorage) {
- _storage_service = window.localStorage;
- _backend = 'localStorage';
- _observer_update = _storage_service.jStorage_update;
- }
- } catch (E3) { /* Firefox fails when touching localStorage and cookies are disabled */ }
- }
- /* Check if browser supports globalStorage */
- else if ('globalStorage' in window) {
- try {
- if (window.globalStorage) {
- if (window.location.hostname == 'localhost') {
- _storage_service = window.globalStorage['localhost.localdomain'];
- } else {
- _storage_service = window.globalStorage[window.location.hostname];
- }
- _backend = 'globalStorage';
- _observer_update = _storage_service.jStorage_update;
- }
- } catch (E4) { /* Firefox fails when touching localStorage and cookies are disabled */ }
- }
- /* Check if browser supports userData behavior */
- else {
- _storage_elm = document.createElement('link');
- if (_storage_elm.addBehavior) {
-
- /* Use a DOM element to act as userData storage */
- _storage_elm.style.behavior = 'url(#default#userData)';
-
- /* userData element needs to be inserted into the DOM! */
- document.getElementsByTagName('head')[0].appendChild(_storage_elm);
-
- try {
- _storage_elm.load('jStorage');
- } catch (E) {
- // try to reset cache
- _storage_elm.setAttribute('jStorage', '{}');
- _storage_elm.save('jStorage');
- _storage_elm.load('jStorage');
- }
-
- var data = '{}';
- try {
- data = _storage_elm.getAttribute('jStorage');
- } catch (E5) {}
-
- try {
- _observer_update = _storage_elm.getAttribute('jStorage_update');
- } catch (E6) {}
-
- _storage_service.jStorage = data;
- _backend = 'userDataBehavior';
- } else {
- _storage_elm = null;
- return;
- }
- }
-
- // Load data from storage
- _load_storage();
-
- // remove dead keys
- _handleTTL();
-
- // start listening for changes
- _setupObserver();
-
- // initialize publish-subscribe service
- _handlePubSub();
-
- // handle cached navigation
- if ('addEventListener' in window) {
- window.addEventListener('pageshow', function(event) {
- if (event.persisted) {
- _storageObserver();
- }
- }, false);
- }
- }
-
- /**
- * Reload data from storage when needed
- */
- function _reloadData() {
- var data = '{}';
-
- if (_backend == 'userDataBehavior') {
- _storage_elm.load('jStorage');
-
- try {
- data = _storage_elm.getAttribute('jStorage');
- } catch (E5) {}
-
- try {
- _observer_update = _storage_elm.getAttribute('jStorage_update');
- } catch (E6) {}
-
- _storage_service.jStorage = data;
- }
-
- _load_storage();
-
- // remove dead keys
- _handleTTL();
-
- _handlePubSub();
- }
-
- /**
- * Sets up a storage change observer
- */
- function _setupObserver() {
- if (_backend == 'localStorage' || _backend == 'globalStorage') {
- if ('addEventListener' in window) {
- window.addEventListener('storage', _storageObserver, false);
- } else {
- document.attachEvent('onstorage', _storageObserver);
- }
- } else if (_backend == 'userDataBehavior') {
- setInterval(_storageObserver, 1000);
- }
- }
-
- /**
- * Fired on any kind of data change, needs to check if anything has
- * really been changed
- */
- function _storageObserver() {
- var updateTime;
- // cumulate change notifications with timeout
- clearTimeout(_observer_timeout);
- _observer_timeout = setTimeout(function() {
-
- if (_backend == 'localStorage' || _backend == 'globalStorage') {
- updateTime = _storage_service.jStorage_update;
- } else if (_backend == 'userDataBehavior') {
- _storage_elm.load('jStorage');
- try {
- updateTime = _storage_elm.getAttribute('jStorage_update');
- } catch (E5) {}
- }
-
- if (updateTime && updateTime != _observer_update) {
- _observer_update = updateTime;
- _checkUpdatedKeys();
- }
-
- }, 25);
- }
-
- /**
- * Reloads the data and checks if any keys are changed
- */
- function _checkUpdatedKeys() {
- var oldCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32)),
- newCrc32List;
-
- _reloadData();
- newCrc32List = JSON.parse(JSON.stringify(_storage.__jstorage_meta.CRC32));
-
- var key,
- updated = [],
- removed = [];
-
- for (key in oldCrc32List) {
- if (oldCrc32List.hasOwnProperty(key)) {
- if (!newCrc32List[key]) {
- removed.push(key);
- continue;
- }
- if (oldCrc32List[key] != newCrc32List[key] && String(oldCrc32List[key]).substr(0, 2) == '2.') {
- updated.push(key);
- }
- }
- }
-
- for (key in newCrc32List) {
- if (newCrc32List.hasOwnProperty(key)) {
- if (!oldCrc32List[key]) {
- updated.push(key);
- }
- }
- }
-
- _fireObservers(updated, 'updated');
- _fireObservers(removed, 'deleted');
- }
-
- /**
- * Fires observers for updated keys
- *
- * @param {Array|String} keys Array of key names or a key
- * @param {String} action What happened with the value (updated, deleted, flushed)
- */
- function _fireObservers(keys, action) {
- keys = [].concat(keys || []);
-
- var i, j, len, jlen;
-
- if (action == 'flushed') {
- keys = [];
- for (var key in _observers) {
- if (_observers.hasOwnProperty(key)) {
- keys.push(key);
- }
- }
- action = 'deleted';
- }
- for (i = 0, len = keys.length; i < len; i++) {
- if (_observers[keys[i]]) {
- for (j = 0, jlen = _observers[keys[i]].length; j < jlen; j++) {
- _observers[keys[i]][j](keys[i], action);
- }
- }
- if (_observers['*']) {
- for (j = 0, jlen = _observers['*'].length; j < jlen; j++) {
- _observers['*'][j](keys[i], action);
- }
- }
- }
- }
-
- /**
- * Publishes key change to listeners
- */
- function _publishChange() {
- var updateTime = (+new Date()).toString();
-
- if (_backend == 'localStorage' || _backend == 'globalStorage') {
- try {
- _storage_service.jStorage_update = updateTime;
- } catch (E8) {
- // safari private mode has been enabled after the jStorage initialization
- _backend = false;
- }
- } else if (_backend == 'userDataBehavior') {
- _storage_elm.setAttribute('jStorage_update', updateTime);
- _storage_elm.save('jStorage');
- }
-
- _storageObserver();
- }
-
- /**
- * Loads the data from the storage based on the supported mechanism
- */
- function _load_storage() {
- /* if jStorage string is retrieved, then decode it */
- if (_storage_service.jStorage) {
- try {
- _storage = JSON.parse(String(_storage_service.jStorage));
- } catch (E6) {
- _storage_service.jStorage = '{}';
- }
- } else {
- _storage_service.jStorage = '{}';
- }
- _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
-
- if (!_storage.__jstorage_meta) {
- _storage.__jstorage_meta = {};
- }
- if (!_storage.__jstorage_meta.CRC32) {
- _storage.__jstorage_meta.CRC32 = {};
- }
- }
-
- /**
- * This functions provides the 'save' mechanism to store the jStorage object
- */
- function _save() {
- _dropOldEvents(); // remove expired events
- try {
- _storage_service.jStorage = JSON.stringify(_storage);
- // If userData is used as the storage engine, additional
- if (_storage_elm) {
- _storage_elm.setAttribute('jStorage', _storage_service.jStorage);
- _storage_elm.save('jStorage');
- }
- _storage_size = _storage_service.jStorage ? String(_storage_service.jStorage).length : 0;
- } catch (E7) { /* probably cache is full, nothing is saved this way*/ }
- }
-
- /**
- * Function checks if a key is set and is string or numberic
- *
- * @param {String} key Key name
- */
- function _checkKey(key) {
- if (typeof key != 'string' && typeof key != 'number') {
- throw new TypeError('Key name must be string or numeric');
- }
- if (key == '__jstorage_meta') {
- throw new TypeError('Reserved key name');
- }
- return true;
- }
-
- /**
- * Removes expired keys
- */
- function _handleTTL() {
- var curtime, i, TTL, CRC32, nextExpire = Infinity,
- changed = false,
- deleted = [];
-
- clearTimeout(_ttl_timeout);
-
- if (!_storage.__jstorage_meta || typeof _storage.__jstorage_meta.TTL != 'object') {
- // nothing to do here
- return;
- }
-
- curtime = +new Date();
- TTL = _storage.__jstorage_meta.TTL;
-
- CRC32 = _storage.__jstorage_meta.CRC32;
- for (i in TTL) {
- if (TTL.hasOwnProperty(i)) {
- if (TTL[i] <= curtime) {
- delete TTL[i];
- delete CRC32[i];
- delete _storage[i];
- changed = true;
- deleted.push(i);
- } else if (TTL[i] < nextExpire) {
- nextExpire = TTL[i];
- }
- }
- }
-
- // set next check
- if (nextExpire != Infinity) {
- _ttl_timeout = setTimeout(_handleTTL, Math.min(nextExpire - curtime, 0x7FFFFFFF));
- }
-
- // save changes
- if (changed) {
- _save();
- _publishChange();
- _fireObservers(deleted, 'deleted');
- }
- }
-
- /**
- * Checks if there's any events on hold to be fired to listeners
- */
- function _handlePubSub() {
- var i, len;
- if (!_storage.__jstorage_meta.PubSub) {
- return;
- }
- var pubelm,
- _pubsubCurrent = _pubsub_last,
- needFired = [];
-
- for (i = len = _storage.__jstorage_meta.PubSub.length - 1; i >= 0; i--) {
- pubelm = _storage.__jstorage_meta.PubSub[i];
- if (pubelm[0] > _pubsub_last) {
- _pubsubCurrent = pubelm[0];
- needFired.unshift(pubelm);
- }
- }
-
- for (i = needFired.length - 1; i >= 0; i--) {
- _fireSubscribers(needFired[i][1], needFired[i][2]);
- }
-
- _pubsub_last = _pubsubCurrent;
- }
-
- /**
- * Fires all subscriber listeners for a pubsub channel
- *
- * @param {String} channel Channel name
- * @param {Mixed} payload Payload data to deliver
- */
- function _fireSubscribers(channel, payload) {
- if (_pubsub_observers[channel]) {
- for (var i = 0, len = _pubsub_observers[channel].length; i < len; i++) {
- // send immutable data that can't be modified by listeners
- try {
- _pubsub_observers[channel][i](channel, JSON.parse(JSON.stringify(payload)));
- } catch (E) {}
- }
- }
- }
-
- /**
- * Remove old events from the publish stream (at least 2sec old)
- */
- function _dropOldEvents() {
- if (!_storage.__jstorage_meta.PubSub) {
- return;
- }
-
- var retire = +new Date() - 2000;
-
- for (var i = 0, len = _storage.__jstorage_meta.PubSub.length; i < len; i++) {
- if (_storage.__jstorage_meta.PubSub[i][0] <= retire) {
- // deleteCount is needed for IE6
- _storage.__jstorage_meta.PubSub.splice(i, _storage.__jstorage_meta.PubSub.length - i);
- break;
- }
- }
-
- if (!_storage.__jstorage_meta.PubSub.length) {
- delete _storage.__jstorage_meta.PubSub;
- }
-
- }
-
- /**
- * Publish payload to a channel
- *
- * @param {String} channel Channel name
- * @param {Mixed} payload Payload to send to the subscribers
- */
- function _publish(channel, payload) {
- if (!_storage.__jstorage_meta) {
- _storage.__jstorage_meta = {};
- }
- if (!_storage.__jstorage_meta.PubSub) {
- _storage.__jstorage_meta.PubSub = [];
- }
-
- _storage.__jstorage_meta.PubSub.unshift([+new Date(), channel, payload]);
-
- _save();
- _publishChange();
- }
-
-
- /**
- * JS Implementation of MurmurHash2
- *
- * SOURCE: https://github.com/garycourt/murmurhash-js (MIT licensed)
- *
- * @author <a href='mailto:gary.court@gmail.com'>Gary Court</a>
- * @see http://github.com/garycourt/murmurhash-js
- * @author <a href='mailto:aappleby@gmail.com'>Austin Appleby</a>
- * @see http://sites.google.com/site/murmurhash/
- *
- * @param {string} str ASCII only
- * @param {number} seed Positive integer only
- * @return {number} 32-bit positive integer hash
- */
-
- function murmurhash2_32_gc(str, seed) {
- var
- l = str.length,
- h = seed ^ l,
- i = 0,
- k;
-
- while (l >= 4) {
- k =
- ((str.charCodeAt(i) & 0xff)) |
- ((str.charCodeAt(++i) & 0xff) << 8) |
- ((str.charCodeAt(++i) & 0xff) << 16) |
- ((str.charCodeAt(++i) & 0xff) << 24);
-
- k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
- k ^= k >>> 24;
- k = (((k & 0xffff) * 0x5bd1e995) + ((((k >>> 16) * 0x5bd1e995) & 0xffff) << 16));
-
- h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16)) ^ k;
-
- l -= 4;
- ++i;
- }
-
- switch (l) {
- case 3:
- h ^= (str.charCodeAt(i + 2) & 0xff) << 16;
- /* falls through */
- case 2:
- h ^= (str.charCodeAt(i + 1) & 0xff) << 8;
- /* falls through */
- case 1:
- h ^= (str.charCodeAt(i) & 0xff);
- h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
- }
-
- h ^= h >>> 13;
- h = (((h & 0xffff) * 0x5bd1e995) + ((((h >>> 16) * 0x5bd1e995) & 0xffff) << 16));
- h ^= h >>> 15;
-
- return h >>> 0;
- }
-
- ////////////////////////// PUBLIC INTERFACE /////////////////////////
-
- $.jStorage = {
- /* Version number */
- version: JSTORAGE_VERSION,
-
- /**
- * Sets a key's value.
- *
- * @param {String} key Key to set. If this value is not set or not
- * a string an exception is raised.
- * @param {Mixed} value Value to set. This can be any value that is JSON
- * compatible (Numbers, Strings, Objects etc.).
- * @param {Object} [options] - possible options to use
- * @param {Number} [options.TTL] - optional TTL value, in milliseconds
- * @return {Mixed} the used value
- */
- set: function(key, value, options) {
- _checkKey(key);
-
- options = options || {};
-
- // undefined values are deleted automatically
- if (typeof value == 'undefined') {
- this.deleteKey(key);
- return value;
- }
-
- if (_XMLService.isXML(value)) {
- value = {
- _is_xml: true,
- xml: _XMLService.encode(value)
- };
- } else if (typeof value == 'function') {
- return undefined; // functions can't be saved!
- } else if (value && typeof value == 'object') {
- // clone the object before saving to _storage tree
- value = JSON.parse(JSON.stringify(value));
- }
-
- _storage[key] = value;
-
- _storage.__jstorage_meta.CRC32[key] = '2.' + murmurhash2_32_gc(JSON.stringify(value), 0x9747b28c);
-
- this.setTTL(key, options.TTL || 0); // also handles saving and _publishChange
-
- _fireObservers(key, 'updated');
- return value;
- },
-
- /**
- * Looks up a key in cache
- *
- * @param {String} key - Key to look up.
- * @param {mixed} def - Default value to return, if key didn't exist.
- * @return {Mixed} the key value, default value or null
- */
- get: function(key, def) {
- _checkKey(key);
- if (key in _storage) {
- if (_storage[key] && typeof _storage[key] == 'object' && _storage[key]._is_xml) {
- return _XMLService.decode(_storage[key].xml);
- } else {
- return _storage[key];
- }
- }
- return typeof(def) == 'undefined' ? null : def;
- },
-
- /**
- * Deletes a key from cache.
- *
- * @param {String} key - Key to delete.
- * @return {Boolean} true if key existed or false if it didn't
- */
- deleteKey: function(key) {
- _checkKey(key);
- if (key in _storage) {
- delete _storage[key];
- // remove from TTL list
- if (typeof _storage.__jstorage_meta.TTL == 'object' &&
- key in _storage.__jstorage_meta.TTL) {
- delete _storage.__jstorage_meta.TTL[key];
- }
-
- delete _storage.__jstorage_meta.CRC32[key];
-
- _save();
- _publishChange();
- _fireObservers(key, 'deleted');
- return true;
- }
- return false;
- },
-
- /**
- * Sets a TTL for a key, or remove it if ttl value is 0 or below
- *
- * @param {String} key - key to set the TTL for
- * @param {Number} ttl - TTL timeout in milliseconds
- * @return {Boolean} true if key existed or false if it didn't
- */
- setTTL: function(key, ttl) {
- var curtime = +new Date();
- _checkKey(key);
- ttl = Number(ttl) || 0;
- if (key in _storage) {
-
- if (!_storage.__jstorage_meta.TTL) {
- _storage.__jstorage_meta.TTL = {};
- }
-
- // Set TTL value for the key
- if (ttl > 0) {
- _storage.__jstorage_meta.TTL[key] = curtime + ttl;
- } else {
- delete _storage.__jstorage_meta.TTL[key];
- }
-
- _save();
-
- _handleTTL();
-
- _publishChange();
- return true;
- }
- return false;
- },
-
- /**
- * Gets remaining TTL (in milliseconds) for a key or 0 when no TTL has been set
- *
- * @param {String} key Key to check
- * @return {Number} Remaining TTL in milliseconds
- */
- getTTL: function(key) {
- var curtime = +new Date(),
- ttl;
- _checkKey(key);
- if (key in _storage && _storage.__jstorage_meta.TTL && _storage.__jstorage_meta.TTL[key]) {
- ttl = _storage.__jstorage_meta.TTL[key] - curtime;
- return ttl || 0;
- }
- return 0;
- },
-
- /**
- * Deletes everything in cache.
- *
- * @return {Boolean} Always true
- */
- flush: function() {
- _storage = {
- __jstorage_meta: {
- CRC32: {}
- }
- };
- _save();
- _publishChange();
- _fireObservers(null, 'flushed');
- return true;
- },
-
- /**
- * Returns a read-only copy of _storage
- *
- * @return {Object} Read-only copy of _storage
- */
- storageObj: function() {
- function F() {}
- F.prototype = _storage;
- return new F();
- },
-
- /**
- * Returns an index of all used keys as an array
- * ['key1', 'key2',..'keyN']
- *
- * @return {Array} Used keys
- */
- index: function() {
- var index = [],
- i;
- for (i in _storage) {
- if (_storage.hasOwnProperty(i) && i != '__jstorage_meta') {
- index.push(i);
- }
- }
- return index;
- },
-
- /**
- * How much space in bytes does the storage take?
- *
- * @return {Number} Storage size in chars (not the same as in bytes,
- * since some chars may take several bytes)
- */
- storageSize: function() {
- return _storage_size;
- },
-
- /**
- * Which backend is currently in use?
- *
- * @return {String} Backend name
- */
- currentBackend: function() {
- return _backend;
- },
-
- /**
- * Test if storage is available
- *
- * @return {Boolean} True if storage can be used
- */
- storageAvailable: function() {
- return !!_backend;
- },
-
- /**
- * Register change listeners
- *
- * @param {String} key Key name
- * @param {Function} callback Function to run when the key changes
- */
- listenKeyChange: function(key, callback) {
- _checkKey(key);
- if (!_observers[key]) {
- _observers[key] = [];
- }
- _observers[key].push(callback);
- },
-
- /**
- * Remove change listeners
- *
- * @param {String} key Key name to unregister listeners against
- * @param {Function} [callback] If set, unregister the callback, if not - unregister all
- */
- stopListening: function(key, callback) {
- _checkKey(key);
-
- if (!_observers[key]) {
- return;
- }
-
- if (!callback) {
- delete _observers[key];
- return;
- }
-
- for (var i = _observers[key].length - 1; i >= 0; i--) {
- if (_observers[key][i] == callback) {
- _observers[key].splice(i, 1);
- }
- }
- },
-
- /**
- * Subscribe to a Publish/Subscribe event stream
- *
- * @param {String} channel Channel name
- * @param {Function} callback Function to run when the something is published to the channel
- */
- subscribe: function(channel, callback) {
- channel = (channel || '').toString();
- if (!channel) {
- throw new TypeError('Channel not defined');
- }
- if (!_pubsub_observers[channel]) {
- _pubsub_observers[channel] = [];
- }
- _pubsub_observers[channel].push(callback);
- },
-
- /**
- * Publish data to an event stream
- *
- * @param {String} channel Channel name
- * @param {Mixed} payload Payload to deliver
- */
- publish: function(channel, payload) {
- channel = (channel || '').toString();
- if (!channel) {
- throw new TypeError('Channel not defined');
- }
-
- _publish(channel, payload);
- },
-
- /**
- * Reloads the data from browser storage
- */
- reInit: function() {
- _reloadData();
- },
-
- /**
- * Removes reference from global objects and saves it as jStorage
- *
- * @param {Boolean} option if needed to save object as simple 'jStorage' in windows context
- */
- noConflict: function(saveInGlobal) {
- delete window.$.jStorage;
-
- if (saveInGlobal) {
- window.jStorage = this;
- }
-
- return this;
- }
- };
-
- // Initialize jStorage
- _init();
-
-})();
+++ /dev/null
-/*!
- * MockJax - jQuery Plugin to Mock Ajax requests
- *
- * Version: 1.4.0
- * Released: 2011-02-04
- * Source: http://github.com/appendto/jquery-mockjax
- * Docs: http://enterprisejquery.com/2010/07/mock-your-ajax-requests-with-mockjax-for-rapid-development
- * Plugin: mockjax
- * Author: Jonathan Sharp (http://jdsharp.com)
- * License: MIT,GPL
- *
- * Copyright (c) 2010 appendTo LLC.
- * Dual licensed under the MIT or GPL licenses.
- * http://appendto.com/open-source-licenses
- */
-(function($) {
- var _ajax = $.ajax,
- mockHandlers = [];
-
- function parseXML(xml) {
- if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
- DOMParser = function() { };
- DOMParser.prototype.parseFromString = function( xmlString ) {
- var doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- doc.loadXML( xmlString );
- return doc;
- };
- }
-
- try {
- var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
- if ( $.isXMLDoc( xmlDoc ) ) {
- var err = $('parsererror', xmlDoc);
- if ( err.length == 1 ) {
- throw('Error: ' + $(xmlDoc).text() );
- }
- } else {
- throw('Unable to parse XML');
- }
- } catch( e ) {
- var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
- $(document).trigger('xmlParseError', [ msg ]);
- return undefined;
- }
- return xmlDoc;
- }
-
- $.extend({
- ajax: function(origSettings) {
- var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings),
- mock = false;
- // Iterate over our mock handlers (in registration order) until we find
- // one that is willing to intercept the request
- $.each(mockHandlers, function(k, v) {
- if ( !mockHandlers[k] ) {
- return;
- }
- var m = null;
- // If the mock was registered with a function, let the function decide if we
- // want to mock this request
- if ( $.isFunction(mockHandlers[k]) ) {
- m = mockHandlers[k](s);
- } else {
- m = mockHandlers[k];
- // Inspect the URL of the request and check if the mock handler's url
- // matches the url for this ajax request
- if ( $.isFunction(m.url.test) ) {
- // The user provided a regex for the url, test it
- if ( !m.url.test( s.url ) ) {
- m = null;
- }
- } else {
- // Look for a simple wildcard '*' or a direct URL match
- var star = m.url.indexOf('*');
- if ( ( m.url != '*' && m.url != s.url && star == -1 ) ||
- ( star > -1 && m.url.substr(0, star) != s.url.substr(0, star) ) ) {
- // The url we tested did not match the wildcard *
- m = null;
- }
- }
- if ( m ) {
- // Inspect the data submitted in the request (either POST body or GET query string)
- if ( m.data && s.data ) {
- var identical = false;
- // Deep inspect the identity of the objects
- (function ident(mock, live) {
- // Test for situations where the data is a querystring (not an object)
- if (typeof live === 'string') {
- // Querystring may be a regex
- identical = $.isFunction( mock.test ) ? mock.test(live) : mock == live;
- return identical;
- }
- $.each(mock, function(k, v) {
- if ( live[k] === undefined ) {
- identical = false;
- return false;
- } else {
- identical = true;
- if ( typeof live[k] == 'object' ) {
- return ident(mock[k], live[k]);
- } else {
- if ( $.isFunction( mock[k].test ) ) {
- identical = mock[k].test(live[k]);
- } else {
- identical = ( mock[k] == live[k] );
- }
- return identical;
- }
- }
- });
- })(m.data, s.data);
- // They're not identical, do not mock this request
- if ( identical == false ) {
- m = null;
- }
- }
- // Inspect the request type
- if ( m && m.type && m.type != s.type ) {
- // The request type doesn't match (GET vs. POST)
- m = null;
- }
- }
- }
- if ( m ) {
- mock = true;
-
- // Handle console logging
- var c = $.extend({}, $.mockjaxSettings, m);
- if ( c.log && $.isFunction(c.log) ) {
- c.log('MOCK ' + s.type.toUpperCase() + ': ' + s.url, $.extend({}, s));
- }
-
- var jsre = /=\?(&|$)/, jsc = (new Date()).getTime();
-
- // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
- // because there isn't an easy hook for the cross domain script tag of jsonp
- if ( s.dataType === "jsonp" ) {
- if ( s.type.toUpperCase() === "GET" ) {
- if ( !jsre.test( s.url ) ) {
- s.url += (rquery.test( s.url ) ? "&" : "?") + (s.jsonp || "callback") + "=?";
- }
- } else if ( !s.data || !jsre.test(s.data) ) {
- s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
- }
- s.dataType = "json";
- }
-
- // Build temporary JSONP function
- if ( s.dataType === "json" && (s.data && jsre.test(s.data) || jsre.test(s.url)) ) {
- jsonp = s.jsonpCallback || ("jsonp" + jsc++);
-
- // Replace the =? sequence both in the query string and the data
- if ( s.data ) {
- s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
- }
-
- s.url = s.url.replace(jsre, "=" + jsonp + "$1");
-
- // We need to make sure
- // that a JSONP style response is executed properly
- s.dataType = "script";
-
- // Handle JSONP-style loading
- window[ jsonp ] = window[ jsonp ] || function( tmp ) {
- data = tmp;
- success();
- complete();
- // Garbage collect
- window[ jsonp ] = undefined;
-
- try {
- delete window[ jsonp ];
- } catch(e) {}
-
- if ( head ) {
- head.removeChild( script );
- }
- };
- }
-
- var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
- parts = rurl.exec( s.url ),
- remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
-
- // Test if we are going to create a script tag (if so, intercept & mock)
- if ( s.dataType === "script" && s.type.toUpperCase() === "GET" && remote ) {
- // Synthesize the mock request for adding a script tag
- var callbackContext = origSettings && origSettings.context || s;
-
- function success() {
- // If a local callback was specified, fire it and pass it the data
- if ( s.success ) {
- s.success.call( callbackContext, ( m.response ? m.response.toString() : m.responseText || ''), status, {} );
- }
-
- // Fire the global callback
- if ( s.global ) {
- trigger( "ajaxSuccess", [{}, s] );
- }
- }
-
- function complete() {
- // Process result
- if ( s.complete ) {
- s.complete.call( callbackContext, {} , status );
- }
-
- // The request was completed
- if ( s.global ) {
- trigger( "ajaxComplete", [{}, s] );
- }
-
- // Handle the global AJAX counter
- if ( s.global && ! --jQuery.active ) {
- jQuery.event.trigger( "ajaxStop" );
- }
- }
-
- function trigger(type, args) {
- (s.context ? jQuery(s.context) : jQuery.event).trigger(type, args);
- }
-
- if ( m.response && $.isFunction(m.response) ) {
- m.response(origSettings);
- } else {
- $.globalEval(m.responseText);
- }
- success();
- complete();
- return false;
- }
- mock = _ajax.call($, $.extend(true, {}, origSettings, {
- // Mock the XHR object
- xhr: function() {
- // Extend with our default mockjax settings
- m = $.extend({}, $.mockjaxSettings, m);
-
- if ( m.contentType ) {
- m.headers['content-type'] = m.contentType;
- }
-
- // Return our mock xhr object
- return {
- status: m.status,
- readyState: 1,
- open: function() { },
- send: function() {
- // This is a substitute for < 1.4 which lacks $.proxy
- var process = (function(that) {
- return function() {
- return (function() {
- // The request has returned
- this.status = m.status;
- this.readyState = 4;
-
- // We have an executable function, call it to give
- // the mock handler a chance to update it's data
- if ( $.isFunction(m.response) ) {
- m.response(origSettings);
- }
- // Copy over our mock to our xhr object before passing control back to
- // jQuery's onreadystatechange callback
- if ( s.dataType == 'json' && ( typeof m.responseText == 'object' ) ) {
- this.responseText = JSON.stringify(m.responseText);
- } else if ( s.dataType == 'xml' ) {
- if ( typeof m.responseXML == 'string' ) {
- this.responseXML = parseXML(m.responseXML);
- } else {
- this.responseXML = m.responseXML;
- }
- } else {
- this.responseText = m.responseText;
- }
- // jQuery < 1.4 doesn't have onreadystate change for xhr
- if ( $.isFunction(this.onreadystatechange) ) {
- this.onreadystatechange( m.isTimeout ? 'timeout' : undefined );
- }
- }).apply(that);
- };
- })(this);
-
- if ( m.proxy ) {
- // We're proxying this request and loading in an external file instead
- _ajax({
- global: false,
- url: m.proxy,
- type: m.proxyType,
- data: m.data,
- dataType: s.dataType,
- complete: function(xhr, txt) {
- m.responseXML = xhr.responseXML;
- m.responseText = xhr.responseText;
- this.responseTimer = setTimeout(process, m.responseTime || 0);
- }
- });
- } else {
- // type == 'POST' || 'GET' || 'DELETE'
- if ( s.async === false ) {
- // TODO: Blocking delay
- process();
- } else {
- this.responseTimer = setTimeout(process, m.responseTime || 50);
- }
- }
- },
- abort: function() {
- clearTimeout(this.responseTimer);
- },
- setRequestHeader: function() { },
- getResponseHeader: function(header) {
- // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
- if ( m.headers && m.headers[header] ) {
- // Return arbitrary headers
- return m.headers[header];
- } else if ( header.toLowerCase() == 'last-modified' ) {
- return m.lastModified || (new Date()).toString();
- } else if ( header.toLowerCase() == 'etag' ) {
- return m.etag || '';
- } else if ( header.toLowerCase() == 'content-type' ) {
- return m.contentType || 'text/plain';
- }
- },
- getAllResponseHeaders: function() {
- var headers = '';
- $.each(m.headers, function(k, v) {
- headers += k + ': ' + v + "\n";
- });
- return headers;
- }
- };
- }
- }));
- return false;
- }
- });
- // We don't have a mock request, trigger a normal request
- if ( !mock ) {
- return _ajax.apply($, arguments);
- } else {
- return mock;
- }
- }
- });
-
- $.mockjaxSettings = {
- //url: null,
- //type: 'GET',
- log: function(msg) {
- window['console'] && window.console.log && window.console.log(msg);
- },
- status: 200,
- responseTime: 500,
- isTimeout: false,
- contentType: 'text/plain',
- response: '',
- responseText: '',
- responseXML: '',
- proxy: '',
- proxyType: 'GET',
-
- lastModified: null,
- etag: '',
- headers: {
- etag: 'IJF@H#@923uf8023hFO@I#H#',
- 'content-type' : 'text/plain'
- }
- };
-
- $.mockjax = function(settings) {
- var i = mockHandlers.length;
- mockHandlers[i] = settings;
- return i;
- };
- $.mockjaxClear = function(i) {
- if ( arguments.length == 1 ) {
- mockHandlers[i] = null;
- } else {
- mockHandlers = [];
- }
- };
-})(jQuery);
+++ /dev/null
-/*!
- * jQuery xmlDOM Plugin v1.0
- * http://outwestmedia.com/jquery-plugins/xmldom/
- *
- * Released: 2009-04-06
- * Version: 1.0
- *
- * Copyright (c) 2009 Jonathan Sharp, Out West Media LLC.
- * Dual licensed under the MIT and GPL licenses.
- * http://docs.jquery.com/License
- */
-(function($) {
- // IE DOMParser wrapper
- if ( window['DOMParser'] == undefined && window.ActiveXObject ) {
- DOMParser = function() { };
- DOMParser.prototype.parseFromString = function( xmlString ) {
- var doc = new ActiveXObject('Microsoft.XMLDOM');
- doc.async = 'false';
- doc.loadXML( xmlString );
- return doc;
- };
- }
-
- $.xmlDOM = function(xml, onErrorFn) {
- try {
- var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
- if ( $.isXMLDoc( xmlDoc ) ) {
- var err = $('parsererror', xmlDoc);
- if ( err.length == 1 ) {
- throw('Error: ' + $(xmlDoc).text() );
- }
- } else {
- throw('Unable to parse XML');
- }
- } catch( e ) {
- var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
- if ( $.isFunction( onErrorFn ) ) {
- onErrorFn( msg );
- } else {
- $(document).trigger('xmlParseError', [ msg ]);
- }
- return $([]);
- }
- return $( xmlDoc );
- };
-})(jQuery);
\ No newline at end of file
* @param {HTMLElement} titleElement Element with the title to update (may be the same as `element`)
*/
function updateTooltipOnElement( element, titleElement ) {
- var oldTitle, parts, regexp, newTitle, accessKeyLabel;
+ var oldTitle, parts, regexp, newTitle, accessKeyLabel,
+ separatorMsg = mw.message( 'word-separator' ).plain();
oldTitle = titleElement.title;
if ( !oldTitle ) {
return;
}
- parts = ( mw.msg( 'word-separator' ) + mw.msg( 'brackets' ) ).split( '$1' );
+ parts = ( separatorMsg + mw.message( 'brackets' ).plain() ).split( '$1' );
regexp = new RegExp( parts.map( mw.RegExp.escape ).join( '.*?' ) + '$' );
newTitle = oldTitle.replace( regexp, '' );
accessKeyLabel = getAccessKeyLabel( element );
if ( accessKeyLabel ) {
// Should be build the same as in Linker::titleAttrib
- newTitle += mw.msg( 'word-separator' ) + mw.msg( 'brackets', accessKeyLabel );
+ newTitle += separatorMsg + mw.message( 'brackets', accessKeyLabel ).plain();
}
if ( oldTitle !== newTitle ) {
titleElement.title = newTitle;
}
}
-/* Align the toggle based on the direction of the content language */
+/* Collapsible elements in the UI (outside of the content area) are not in either .mw-content-ltr or
+ * .mw-content-rtl. Align them based on the user language. */
+.mw-collapsible:not( @{exclude} ) th:before,
+.mw-collapsible:not( @{exclude} ):before,
+.mw-collapsible-toggle {
+ float: right;
+}
+
+/* For collapsible elements in the content area, override the alginment based on the content language. */
/* @noflip */
.mw-content-ltr,
.mw-content-rtl .mw-content-ltr {
unicode-bidi: embed;
}
+.mw-diff-slot-header {
+ text-align: center;
+}
+
/*!
* Wikidiff2 rendering for moved paragraphs
*/
*/
inspect.dumpTable = function ( data ) {
try {
- // Bartosz made me put this here.
- if ( window.opera ) { throw window.opera; }
// Use Function.prototype#call to force an exception on Firefox,
// which doesn't define console#table but doesn't complain if you
// try to invoke it.
} catch ( e ) {}
try {
console.log( JSON.stringify( data, null, 2 ) );
- return;
} catch ( e ) {}
- mw.log( data );
};
/**
} else if ( summaryByteLimit ) {
mw.widgets.visibleByteLimit( wpReason, summaryByteLimit );
}
- // Infuse for nicer "help" popup
- if ( $( '#wpMovetalk-field' ).length ) {
- OO.ui.infuse( $( '#wpMovetalk-field' ) );
- }
} );
}( mediaWiki, jQuery ) );
*/
/* global Uint32Array */
( function ( mw, $ ) {
- var userInfoPromise, stickyRandomSessionId;
+ var userInfoPromise, pageviewRandomId;
/**
* Get the current user's groups or rights
/**
* A sticky generateRandomSessionId for the current JS execution context,
- * cached within this class.
+ * cached within this class (also known as a page view token).
*
+ * @since 1.32
* @return {string} 64 bit integer in hex format, padded
*/
- stickyRandomId: function () {
- if ( !stickyRandomSessionId ) {
- stickyRandomSessionId = mw.user.generateRandomSessionId();
+ getPageviewToken: function () {
+ if ( !pageviewRandomId ) {
+ pageviewRandomId = mw.user.generateRandomSessionId();
}
- return stickyRandomSessionId;
+ return pageviewRandomId;
},
/**
}
} );
+ /**
+ * @method stickyRandomId
+ * @deprecated since 1.32 use getPageviewToken instead
+ */
+ mw.log.deprecate( mw.user, 'stickyRandomId', mw.user.getPageviewToken, 'Please use getPageviewToken instead' );
+
}( mediaWiki, jQuery ) );
// Hardcoded exceptions for browsers that pass the requirement but we don't want to
// support in the modern run-time.
// Note: Please extend the regex instead of adding new ones
- !(
- ua.match( /MSIE 10|webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass|^Mozilla\/5\.0 .+ Gecko\/$|googleweblight/ ) ||
- ua.match( /PlayStation/i )
- )
+ !ua.match( /MSIE 10|webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass|^Mozilla\/5\.0 .+ Gecko\/$|googleweblight|PLAYSTATION|PlayStation/ )
);
};
'TestDeprecatedSubclass' => "$testDir/phpunit/includes/debug/TestDeprecatedSubclass.php",
# tests/phpunit/includes/diff
+ 'CustomDifferenceEngine' => "$testDir/phpunit/includes/diff/CustomDifferenceEngine.php",
'FakeDiffOp' => "$testDir/phpunit/includes/diff/FakeDiffOp.php",
# tests/phpunit/includes/externalstore
+++ /dev/null
-# Changes
-DELETE {
-?category ?x ?y
-} WHERE {
- VALUES ?category {
- <http://acme.test/wiki/Category:Changed_category>
- }
-};
-INSERT DATA {
-
-<http://acme.test/wiki/Category:Changed_category> a mediawiki:Category ;
- rdfs:label "Changed category" ;
- mediawiki:pages "7"^^xsd:integer ;
- mediawiki:subcategories "2"^^xsd:integer ;
- mediawiki:isInCategory <http://acme.test/wiki/Category:Parent_of_30> .
-
-};
--- /dev/null
+# Edits
+DELETE {
+?category ?x ?y
+} WHERE {
+ VALUES ?category {
+ <http://acme.test/wiki/Category:Changed_category>
+ }
+};
+INSERT DATA {
+
+<http://acme.test/wiki/Category:Changed_category> a mediawiki:Category ;
+ rdfs:label "Changed category" ;
+ mediawiki:pages "7"^^xsd:integer ;
+ mediawiki:subcategories "2"^^xsd:integer ;
+ mediawiki:isInCategory <http://acme.test/wiki/Category:Parent_of_30> .
+
+};
* per T28425
* @covers Block::__construct
*/
- public function testBug26425BlockTimestampDefaultsToTime() {
+ public function testT28425BlockTimestampDefaultsToTime() {
$user = $this->getUserForBlocking();
$block = $this->addBlockForUser( $user );
$madeAt = wfTimestamp( TS_MW );
* because the new function didn't accept empty strings like Block::load()
* had. Regression T31116.
*
- * @dataProvider provideBug29116Data
+ * @dataProvider provideT31116Data
* @covers Block::newFromTarget
*/
- public function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) {
+ public function testT31116NewFromTargetWithEmptyIp( $vagueTarget ) {
$user = $this->getUserForBlocking();
$initialBlock = $this->addBlockForUser( $user );
$block = Block::newFromTarget( $user->getName(), $vagueTarget );
);
}
- public static function provideBug29116Data() {
+ public static function provideT31116Data() {
return [
[ null ],
[ '' ],
* @covers ::wfShellExec
*/
class WfShellExecTest extends MediaWikiTestCase {
- public function testBug67870() {
+ public function testT69870() {
$command = wfIsWindows()
// 333 = 331 + CRLF
? ( 'for /l %i in (1, 1, 1001) do @echo ' . str_repeat( '*', 331 ) )
Html::errorBox( 'err', 'heading' ),
'<div class="errorbox"><h2>heading</h2>err</div>'
);
+ $this->assertEquals(
+ Html::errorBox( 'err', '0' ),
+ '<div class="errorbox"><h2>0</h2>err</div>'
+ );
}
/**
"no edit conflict expected here" );
}
- public function testEditConflict_bug41990() {
+ public function testEditConflict_T43990() {
static $count = 0;
$count++;
*/
// assume NS_HELP defaults to wikitext
- $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count";
+ $name = "Help:ApiEditPageTest_testEditConflict_redirect_T43990_$count";
$title = Title::newFromText( $name );
$page = WikiPage::factory( $title );
- $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count";
+ $rname = "Help:ApiEditPageTest_testEditConflict_redirect_T43990_r$count";
$rtitle = Title::newFromText( $rname );
$rpage = WikiPage::factory( $rtitle );
<?php
use MediaWiki\MediaWikiServices;
+use Wikimedia\TestingAccessWrapper;
/**
* @group ContentHandler
} );
$this->assertContains( 'Ferrari', ContentHandler::getContentModels() );
}
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_default() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test default renderer
+ $contentHandler = new WikitextContentHandler( CONTENT_MODEL_WIKITEXT );
+ $slotDiffRenderer = $contentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertInstanceOf( TextSlotDiffRenderer::class, $slotDiffRenderer );
+ }
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_bc() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test B/C renderer
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ // hack to track object identity across cloning
+ $customDifferenceEngine->objectId = 12345;
+ $customContentHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'foo', [] ] )
+ ->setMethods( [ 'createDifferenceEngine' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ /** @var $customContentHandler ContentHandler */
+ $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertInstanceOf( DifferenceEngineSlotDiffRenderer::class, $slotDiffRenderer );
+ $this->assertSame(
+ $customDifferenceEngine->objectId,
+ TestingAccessWrapper::newFromObject( $slotDiffRenderer )->differenceEngine->objectId
+ );
+ }
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_nobc() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test that B/C renderer does not get used when getSlotDiffRendererInternal is overridden
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $customSlotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $customContentHandler2 = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'bar', [] ] )
+ ->setMethods( [ 'createDifferenceEngine', 'getSlotDiffRendererInternal' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'getSlotDiffRendererInternal' )
+ ->willReturn( $customSlotDiffRenderer );
+ /** @var $customContentHandler2 ContentHandler */
+ $slotDiffRenderer = $customContentHandler2->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertSame( $customSlotDiffRenderer, $slotDiffRenderer );
+ }
+
+ /**
+ * @covers ContentHandler::getSlotDiffRenderer
+ */
+ public function testGetSlotDiffRenderer_hook() {
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'GetSlotDiffRenderer' => [],
+ ] );
+
+ // test that the hook handler takes precedence
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->disableOriginalConstructor()
+ ->getMock();
+ $customContentHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'foo', [] ] )
+ ->setMethods( [ 'createDifferenceEngine' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ /** @var $customContentHandler ContentHandler */
+
+ $customSlotDiffRenderer = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $customContentHandler2 = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'bar', [] ] )
+ ->setMethods( [ 'createDifferenceEngine', 'getSlotDiffRendererInternal' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ $customContentHandler2->expects( $this->any() )
+ ->method( 'getSlotDiffRendererInternal' )
+ ->willReturn( $customSlotDiffRenderer );
+ /** @var $customContentHandler2 ContentHandler */
+
+ $customSlotDiffRenderer2 = $this->getMockBuilder( SlotDiffRenderer::class )
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->setTemporaryHook( 'GetSlotDiffRenderer',
+ function ( $handler, &$slotDiffRenderer ) use ( $customSlotDiffRenderer2 ) {
+ $slotDiffRenderer = $customSlotDiffRenderer2;
+ } );
+
+ $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertSame( $customSlotDiffRenderer2, $slotDiffRenderer );
+ $slotDiffRenderer = $customContentHandler2->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->assertSame( $customSlotDiffRenderer2, $slotDiffRenderer );
+ }
+
}
--- /dev/null
+<?php
+
+class CustomDifferenceEngine extends DifferenceEngine {
+
+ public function __construct() {
+ parent::__construct();
+ }
+
+ public function generateContentDiffBody( Content $old, Content $new ) {
+ return $old->getNativeData() . '|' . $new->getNativeData();
+ }
+
+ public function showDiffStyle() {
+ $this->getOutput()->addModules( 'foo' );
+ }
+
+ public function getDiffBodyCacheKeyParams() {
+ $params = parent::getDiffBodyCacheKeyParams();
+ $params[] = 'foo';
+ return $params;
+ }
+
+}
--- /dev/null
+<?php
+
+/**
+ * @covers DifferenceEngineSlotDiffRenderer
+ */
+class DifferenceEngineSlotDiffRendererTest extends \PHPUnit\Framework\TestCase {
+
+ public function testGetDiff() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ $this->assertEquals( 'xxx|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( null, $newContent );
+ $this->assertEquals( '|yyy', $diff );
+
+ $diff = $slotDiffRenderer->getDiff( $oldContent, null );
+ $this->assertEquals( 'xxx|', $diff );
+ }
+
+ public function testAddModules() {
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->disableOriginalConstructor()
+ ->setMethods( [ 'addModules' ] )
+ ->getMock();
+ $output->expects( $this->once() )
+ ->method( 'addModules' )
+ ->with( 'foo' );
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $slotDiffRenderer->addModules( $output );
+ }
+
+ public function testGetExtraCacheKeys() {
+ $differenceEngine = new CustomDifferenceEngine();
+ $slotDiffRenderer = new DifferenceEngineSlotDiffRenderer( $differenceEngine );
+ $extraCacheKeys = $slotDiffRenderer->getExtraCacheKeys();
+ $this->assertSame( [ 'foo' ], $extraCacheKeys );
+ }
+
+}
<?php
+use MediaWiki\Storage\MutableRevisionRecord;
+use MediaWiki\Storage\RevisionRecord;
+use MediaWiki\Storage\SlotRecord;
use Wikimedia\TestingAccessWrapper;
/**
$this->assertEquals( $expected, $diffEngine->addLocalisedTitleTooltips( $input ) );
}
+ /**
+ * @dataProvider provideGenerateContentDiffBody
+ */
+ public function testGenerateContentDiffBody(
+ Content $oldContent, Content $newContent, $expectedDiff
+ ) {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ $differenceEngine = new DifferenceEngine();
+ $diff = $differenceEngine->generateContentDiffBody( $oldContent, $newContent );
+ $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
+ }
+
+ public function provideGenerateContentDiffBody() {
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
+ 'testing-nontext' => DummyNonTextContentHandler::class,
+ ] );
+ $content1 = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $content2 = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ return [
+ 'self-diff' => [ $content1, $content1, '' ],
+ 'text diff' => [ $content1, $content2, '-xxx+yyy' ],
+ ];
+ }
+
+ public function testGenerateTextDiffBody() {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ $oldText = "aaa\nbbb\nccc";
+ $newText = "aaa\nxxx\nccc";
+ $expectedDiff = " aaa aaa\n-bbb+xxx\n ccc ccc";
+
+ $differenceEngine = new DifferenceEngine();
+ $diff = $differenceEngine->generateTextDiffBody( $oldText, $newText );
+ $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
+ }
+
+ public function testSetContent() {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ $oldContent = ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT );
+ $newContent = ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT );
+
+ $differenceEngine = new DifferenceEngine();
+ $differenceEngine->setContent( $oldContent, $newContent );
+ $diff = $differenceEngine->getDiffBody();
+ $this->assertSame( "Line 1:\nLine 1:\n-xxx+yyy", $this->getPlainDiff( $diff ) );
+ }
+
+ public function testSetRevisions() {
+ $main1 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT ) );
+ $main2 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT ) );
+ $rev1 = $this->getRevisionRecord( $main1 );
+ $rev2 = $this->getRevisionRecord( $main2 );
+
+ $differenceEngine = new DifferenceEngine();
+ $differenceEngine->setRevisions( $rev1, $rev2 );
+ $this->assertSame( $rev1, $differenceEngine->getOldRevision() );
+ $this->assertSame( $rev2, $differenceEngine->getNewRevision() );
+ $this->assertSame( true, $differenceEngine->loadRevisionData() );
+ $this->assertSame( true, $differenceEngine->loadText() );
+
+ $differenceEngine->setRevisions( null, $rev2 );
+ $this->assertSame( null, $differenceEngine->getOldRevision() );
+ }
+
+ /**
+ * @dataProvider provideGetDiffBody
+ */
+ public function testGetDiffBody(
+ RevisionRecord $oldRevision = null, RevisionRecord $newRevision = null, $expectedDiff
+ ) {
+ // Set $wgExternalDiffEngine to something bogus to try to force use of
+ // the PHP engine rather than wikidiff2.
+ $this->setMwGlobals( [
+ 'wgExternalDiffEngine' => '/dev/null',
+ ] );
+
+ if ( $expectedDiff instanceof Exception ) {
+ $this->setExpectedException( get_class( $expectedDiff ), $expectedDiff->getMessage() );
+ }
+ $differenceEngine = new DifferenceEngine();
+ $differenceEngine->setRevisions( $oldRevision, $newRevision );
+ if ( $expectedDiff instanceof Exception ) {
+ return;
+ }
+
+ $diff = $differenceEngine->getDiffBody();
+ $this->assertSame( $expectedDiff, $this->getPlainDiff( $diff ) );
+ }
+
+ public function provideGetDiffBody() {
+ $main1 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'xxx', null, CONTENT_MODEL_TEXT ) );
+ $main2 = SlotRecord::newUnsaved( 'main',
+ ContentHandler::makeContent( 'yyy', null, CONTENT_MODEL_TEXT ) );
+ $slot1 = SlotRecord::newUnsaved( 'slot',
+ ContentHandler::makeContent( 'aaa', null, CONTENT_MODEL_TEXT ) );
+ $slot2 = SlotRecord::newUnsaved( 'slot',
+ ContentHandler::makeContent( 'bbb', null, CONTENT_MODEL_TEXT ) );
+
+ return [
+ 'revision vs. null' => [
+ null,
+ $this->getRevisionRecord( $main1, $slot1 ),
+ '',
+ ],
+ 'revision vs. itself' => [
+ $this->getRevisionRecord( $main1, $slot1 ),
+ $this->getRevisionRecord( $main1, $slot1 ),
+ '',
+ ],
+ 'different text in one slot' => [
+ $this->getRevisionRecord( $main1, $slot1 ),
+ $this->getRevisionRecord( $main1, $slot2 ),
+ "slotLine 1:\nLine 1:\n-aaa+bbb",
+ ],
+ 'different text in two slots' => [
+ $this->getRevisionRecord( $main1, $slot1 ),
+ $this->getRevisionRecord( $main2, $slot2 ),
+ "Line 1:\nLine 1:\n-xxx+yyy\nslotLine 1:\nLine 1:\n-aaa+bbb",
+ ],
+ 'new slot' => [
+ $this->getRevisionRecord( $main1 ),
+ $this->getRevisionRecord( $main1, $slot1 ),
+ "slotLine 1:\nLine 1:\n- +aaa",
+ ],
+ ];
+ }
+
+ public function testRecursion() {
+ // Set up a ContentHandler which will return a wrapped DifferenceEngine as
+ // SlotDiffRenderer, then pass it a content which uses the same ContentHandler.
+ // This tests the anti-recursion logic in DifferenceEngine::generateContentDiffBody.
+
+ $customDifferenceEngine = $this->getMockBuilder( DifferenceEngine::class )
+ ->enableProxyingToOriginalMethods()
+ ->getMock();
+ $customContentHandler = $this->getMockBuilder( ContentHandler::class )
+ ->setConstructorArgs( [ 'foo', [] ] )
+ ->setMethods( [ 'createDifferenceEngine' ] )
+ ->getMockForAbstractClass();
+ $customContentHandler->expects( $this->any() )
+ ->method( 'createDifferenceEngine' )
+ ->willReturn( $customDifferenceEngine );
+ /** @var $customContentHandler ContentHandler */
+ $customContent = $this->getMockBuilder( Content::class )
+ ->setMethods( [ 'getContentHandler' ] )
+ ->getMockForAbstractClass();
+ $customContent->expects( $this->any() )
+ ->method( 'getContentHandler' )
+ ->willReturn( $customContentHandler );
+ /** @var $customContent Content */
+ $customContent2 = clone $customContent;
+
+ $slotDiffRenderer = $customContentHandler->getSlotDiffRenderer( RequestContext::getMain() );
+ $this->setExpectedException( Exception::class,
+ ': could not maintain backwards compatibility. Please use a SlotDiffRenderer.' );
+ $slotDiffRenderer->getDiff( $customContent, $customContent2 );
+ }
+
+ /**
+ * Convert a HTML diff to a human-readable format and hopefully make the test less fragile.
+ * @param string diff
+ * @return string
+ */
+ private function getPlainDiff( $diff ) {
+ $replacements = [
+ html_entity_decode( ' ' ) => ' ',
+ html_entity_decode( '−' ) => '-',
+ ];
+ return str_replace( array_keys( $replacements ), array_values( $replacements ),
+ trim( strip_tags( $diff ), "\n" ) );
+ }
+
+ /**
+ * @param SlotRecord[] $slots
+ * @return MutableRevisionRecord
+ */
+ private function getRevisionRecord( ...$slots ) {
+ $title = Title::newFromText( 'Foo' );
+ $revision = new MutableRevisionRecord( $title );
+ foreach ( $slots as $slot ) {
+ $revision->setSlot( $slot );
+ }
+ return $revision;
+ }
+
}
--- /dev/null
+<?php
+
+/**
+ * @covers TextSlotDiffRenderer
+ */
+class TextSlotDiffRendererTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideGetDiff
+ * @param Content|null $oldContent
+ * @param Content|null $newContent
+ * @param string|Exception $expectedResult
+ * @throws Exception
+ */
+ public function testGetDiff(
+ Content $oldContent = null, Content $newContent = null, $expectedResult
+ ) {
+ if ( $expectedResult instanceof Exception ) {
+ $this->setExpectedException( get_class( $expectedResult ), $expectedResult->getMessage() );
+ }
+
+ $slotDiffRenderer = $this->getTextSlotDiffRenderer();
+ $diff = $slotDiffRenderer->getDiff( $oldContent, $newContent );
+ if ( $expectedResult instanceof Exception ) {
+ return;
+ }
+ $plainDiff = $this->getPlainDiff( $diff );
+ $this->assertSame( $expectedResult, $plainDiff );
+ }
+
+ public function provideGetDiff() {
+ $this->mergeMwGlobalArrayValue( 'wgContentHandlers', [
+ 'testing' => DummyContentHandlerForTesting::class,
+ 'testing-nontext' => DummyNonTextContentHandler::class,
+ ] );
+
+ return [
+ 'same text' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ "",
+ ],
+ 'different text' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ $this->makeContent( "aaa\nxxx\nccc" ),
+ " aaa aaa\n-bbb+xxx\n ccc ccc",
+ ],
+ 'no right content' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ null,
+ "-aaa+ \n-bbb \n-ccc ",
+ ],
+ 'no left content' => [
+ null,
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ "- +aaa\n +bbb\n +ccc",
+ ],
+ 'no content' => [
+ null,
+ null,
+ new InvalidArgumentException( '$oldContent and $newContent cannot both be null' ),
+ ],
+ 'non-text left content' => [
+ $this->makeContent( '', 'testing-nontext' ),
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ new InvalidArgumentException( 'TextSlotDiffRenderer does not handle DummyNonTextContent' ),
+ ],
+ 'non-text right content' => [
+ $this->makeContent( "aaa\nbbb\nccc" ),
+ $this->makeContent( '', 'testing-nontext' ),
+ new InvalidArgumentException( 'TextSlotDiffRenderer does not handle DummyNonTextContent' ),
+ ],
+ ];
+ }
+
+ // no separate test for getTextDiff() as getDiff() is just a thin wrapper around it
+
+ /**
+ * @return TextSlotDiffRenderer
+ */
+ private function getTextSlotDiffRenderer() {
+ $slotDiffRenderer = new TextSlotDiffRenderer();
+ $slotDiffRenderer->setStatsdDataFactory( new NullStatsdDataFactory() );
+ $slotDiffRenderer->setLanguage( Language::factory( 'en' ) );
+ $slotDiffRenderer->setWikiDiff2MovedParagraphDetectionCutoff( 0 );
+ $slotDiffRenderer->setEngine( TextSlotDiffRenderer::ENGINE_PHP );
+ return $slotDiffRenderer;
+ }
+
+ /**
+ * Convert a HTML diff to a human-readable format and hopefully make the test less fragile.
+ * @param string diff
+ * @return string
+ */
+ private function getPlainDiff( $diff ) {
+ $replacements = [
+ html_entity_decode( ' ' ) => ' ',
+ html_entity_decode( '−' ) => '-',
+ ];
+ return str_replace( array_keys( $replacements ), array_values( $replacements ),
+ trim( strip_tags( $diff ), "\n" ) );
+ }
+
+ /**
+ * @param string $str
+ * @param string $model
+ * @return null|TextContent
+ */
+ private function makeContent( $str, $model = CONTENT_MODEL_TEXT ) {
+ return ContentHandler::makeContent( $str, null, $model );
+ }
+
+}
* Test specifications by Alexandre "ialex" Emsenhuber.
* @todo give this test a real name explaining what is being tested here
*/
- public function testBug41337() {
+ public function testT43337() {
// Set a low limit
$this->setMwGlobals( 'wgMaxSigChars', 2 );
// getGenderCache() provides a mock that considers first
// names ending in "a" to be female.
[ NS_USER, 'Lisa_Müller', '', 'de', 'Benutzerin:Lisa Müller' ],
- [ 1000000, 'Invalid_namespace', '', 'en', ':Invalid namespace' ],
+ [ 1000000, 'Invalid_namespace', '', 'en', 'Special:Badtitle/NS1000000:Invalid namespace' ],
];
}
[ NS_MAIN, 'Remote_page', '', 'remotetestiw', 'en', 'remotetestiw:Remote_page' ],
// non-existent namespace
- [ 10000000, 'Foobar', '', '', 'en', ':Foobar' ],
+ [ 10000000, 'Foobar', '', '', 'en', 'Special:Badtitle/NS10000000:Foobar' ],
];
}
/**
* @todo give this test a real name explaining what is being tested here
*/
- public function testBug29408() {
+ public function testT31408() {
$this->setMwGlobals( 'wgUser', self::$users['uploader']->getUser() );
$repo = RepoGroup::singleton()->getLocalRepo();
],
[ 22 => true ],
],
- 'change in categories' => [
- __DIR__ . "/../data/categoriesrdf/change.sparql",
+ 'edit category' => [
+ __DIR__ . "/../data/categoriesrdf/edit.sparql",
'getChangedCatsIterator',
- 'handleChanges',
+ 'handleEdits',
[
(object)[
'rc_title' => 'Changed category',
'rc_title' => 'Changed again',
'rc_cur_id' => 30,
'pp_propname' => null,
- 'cat_pages' => 10,
+ 'cat_pages' => 12,
'cat_subcats' => 2,
'cat_files' => 1,
],
],
[ 31 => true ],
],
-
+ // TODO: not sure how to test categorization changes, it uses the database select...
];
}
assert.notEqual( result, result2, 'different when called multiple times' );
} );
- QUnit.test( 'stickyRandomId', function ( assert ) {
- var result = mw.user.stickyRandomId(),
- result2 = mw.user.stickyRandomId();
+ QUnit.test( 'getPageviewToken', function ( assert ) {
+ var result = mw.user.getPageviewToken(),
+ result2 = mw.user.getPageviewToken();
assert.strictEqual( typeof result, 'string', 'type' );
assert.strictEqual( /^[a-f0-9]{16}$/.test( result ), true, '16 HEX symbols string' );
assert.strictEqual( result2, result, 'sticky' );