forked from CIT/Vmeda.Online
		
	
						commit
						b5b8e566ac
					
				@ -204,7 +204,7 @@ const appConfig = {
 | 
			
		||||
        'no-fallthrough': 'off',
 | 
			
		||||
        'no-invalid-this': 'error',
 | 
			
		||||
        'no-irregular-whitespace': 'error',
 | 
			
		||||
        'no-multiple-empty-lines': 'error',
 | 
			
		||||
        'no-multiple-empty-lines': ['error', { "max": 1 }],
 | 
			
		||||
        'no-new-wrappers': 'error',
 | 
			
		||||
        'no-sequences': 'error',
 | 
			
		||||
        'no-trailing-spaces': 'error',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										244
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										244
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@ -2039,9 +2039,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@eslint/eslintrc": {
 | 
			
		||||
      "version": "0.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==",
 | 
			
		||||
      "version": "0.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "ajv": "^6.12.4",
 | 
			
		||||
@ -2051,15 +2051,14 @@
 | 
			
		||||
        "ignore": "^4.0.6",
 | 
			
		||||
        "import-fresh": "^3.2.1",
 | 
			
		||||
        "js-yaml": "^3.13.1",
 | 
			
		||||
        "lodash": "^4.17.19",
 | 
			
		||||
        "minimatch": "^3.0.4",
 | 
			
		||||
        "strip-json-comments": "^3.1.1"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "debug": {
 | 
			
		||||
          "version": "4.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
 | 
			
		||||
          "version": "4.3.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
 | 
			
		||||
          "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ms": "2.1.2"
 | 
			
		||||
@ -4915,9 +4914,9 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "astral-regex": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "async": {
 | 
			
		||||
@ -8780,13 +8779,13 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "eslint": {
 | 
			
		||||
      "version": "7.10.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz",
 | 
			
		||||
      "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==",
 | 
			
		||||
      "version": "7.21.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.21.0.tgz",
 | 
			
		||||
      "integrity": "sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@babel/code-frame": "^7.0.0",
 | 
			
		||||
        "@eslint/eslintrc": "^0.1.3",
 | 
			
		||||
        "@babel/code-frame": "7.12.11",
 | 
			
		||||
        "@eslint/eslintrc": "^0.4.0",
 | 
			
		||||
        "ajv": "^6.10.0",
 | 
			
		||||
        "chalk": "^4.0.0",
 | 
			
		||||
        "cross-spawn": "^7.0.2",
 | 
			
		||||
@ -8795,11 +8794,11 @@
 | 
			
		||||
        "enquirer": "^2.3.5",
 | 
			
		||||
        "eslint-scope": "^5.1.1",
 | 
			
		||||
        "eslint-utils": "^2.1.0",
 | 
			
		||||
        "eslint-visitor-keys": "^1.3.0",
 | 
			
		||||
        "espree": "^7.3.0",
 | 
			
		||||
        "esquery": "^1.2.0",
 | 
			
		||||
        "eslint-visitor-keys": "^2.0.0",
 | 
			
		||||
        "espree": "^7.3.1",
 | 
			
		||||
        "esquery": "^1.4.0",
 | 
			
		||||
        "esutils": "^2.0.2",
 | 
			
		||||
        "file-entry-cache": "^5.0.1",
 | 
			
		||||
        "file-entry-cache": "^6.0.1",
 | 
			
		||||
        "functional-red-black-tree": "^1.0.1",
 | 
			
		||||
        "glob-parent": "^5.0.0",
 | 
			
		||||
        "globals": "^12.1.0",
 | 
			
		||||
@ -8810,7 +8809,7 @@
 | 
			
		||||
        "js-yaml": "^3.13.1",
 | 
			
		||||
        "json-stable-stringify-without-jsonify": "^1.0.1",
 | 
			
		||||
        "levn": "^0.4.1",
 | 
			
		||||
        "lodash": "^4.17.19",
 | 
			
		||||
        "lodash": "^4.17.20",
 | 
			
		||||
        "minimatch": "^3.0.4",
 | 
			
		||||
        "natural-compare": "^1.4.0",
 | 
			
		||||
        "optionator": "^0.9.1",
 | 
			
		||||
@ -8819,11 +8818,20 @@
 | 
			
		||||
        "semver": "^7.2.1",
 | 
			
		||||
        "strip-ansi": "^6.0.0",
 | 
			
		||||
        "strip-json-comments": "^3.1.0",
 | 
			
		||||
        "table": "^5.2.3",
 | 
			
		||||
        "table": "^6.0.4",
 | 
			
		||||
        "text-table": "^0.2.0",
 | 
			
		||||
        "v8-compile-cache": "^2.0.3"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/code-frame": {
 | 
			
		||||
          "version": "7.12.11",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
 | 
			
		||||
          "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "@babel/highlight": "^7.10.4"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "ansi-regex": {
 | 
			
		||||
          "version": "5.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
 | 
			
		||||
@ -8841,9 +8849,9 @@
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "debug": {
 | 
			
		||||
          "version": "4.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
 | 
			
		||||
          "version": "4.3.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
 | 
			
		||||
          "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ms": "2.1.2"
 | 
			
		||||
@ -8859,12 +8867,6 @@
 | 
			
		||||
            "estraverse": "^4.1.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "eslint-visitor-keys": {
 | 
			
		||||
          "version": "1.3.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
 | 
			
		||||
          "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "globals": {
 | 
			
		||||
          "version": "12.4.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz",
 | 
			
		||||
@ -9069,13 +9071,13 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "espree": {
 | 
			
		||||
      "version": "7.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==",
 | 
			
		||||
      "version": "7.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "acorn": "^7.4.0",
 | 
			
		||||
        "acorn-jsx": "^5.2.0",
 | 
			
		||||
        "acorn-jsx": "^5.3.1",
 | 
			
		||||
        "eslint-visitor-keys": "^1.3.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
@ -9100,9 +9102,9 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "esquery": {
 | 
			
		||||
      "version": "1.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
 | 
			
		||||
      "version": "1.4.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "estraverse": "^5.1.0"
 | 
			
		||||
@ -9548,12 +9550,12 @@
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "file-entry-cache": {
 | 
			
		||||
      "version": "5.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
 | 
			
		||||
      "version": "6.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "flat-cache": "^2.0.1"
 | 
			
		||||
        "flat-cache": "^3.0.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "file-loader": {
 | 
			
		||||
@ -9811,31 +9813,19 @@
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "flat-cache": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
 | 
			
		||||
      "version": "3.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "flatted": "^2.0.0",
 | 
			
		||||
        "rimraf": "2.6.3",
 | 
			
		||||
        "write": "1.0.3"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "rimraf": {
 | 
			
		||||
          "version": "2.6.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
 | 
			
		||||
          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "glob": "^7.1.3"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        "flatted": "^3.1.0",
 | 
			
		||||
        "rimraf": "^3.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "flatted": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
 | 
			
		||||
      "version": "3.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "flush-write-stream": {
 | 
			
		||||
@ -18084,6 +18074,12 @@
 | 
			
		||||
      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "require-from-string": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "require-main-filename": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
 | 
			
		||||
@ -19109,38 +19105,20 @@
 | 
			
		||||
      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="
 | 
			
		||||
    },
 | 
			
		||||
    "slice-ansi": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "ansi-styles": "^3.2.0",
 | 
			
		||||
        "astral-regex": "^1.0.0",
 | 
			
		||||
        "is-fullwidth-code-point": "^2.0.0"
 | 
			
		||||
        "ansi-styles": "^4.0.0",
 | 
			
		||||
        "astral-regex": "^2.0.0",
 | 
			
		||||
        "is-fullwidth-code-point": "^3.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ansi-styles": {
 | 
			
		||||
          "version": "3.2.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
 | 
			
		||||
          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "color-convert": "^1.9.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "color-convert": {
 | 
			
		||||
          "version": "1.9.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
 | 
			
		||||
          "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "color-name": "1.1.3"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "color-name": {
 | 
			
		||||
          "version": "1.1.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
 | 
			
		||||
          "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
 | 
			
		||||
        "is-fullwidth-code-point": {
 | 
			
		||||
          "version": "3.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@ -20297,26 +20275,71 @@
 | 
			
		||||
      "integrity": "sha512-3ozUwGSf5jmrhGgOXlX/O6hk1KQ28XPb7d3NiPZX267QmimuDq3TuIgnkw+vICUrGJGKWPLKmXVASnuJ3w07nw=="
 | 
			
		||||
    },
 | 
			
		||||
    "table": {
 | 
			
		||||
      "version": "5.4.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
 | 
			
		||||
      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
 | 
			
		||||
      "version": "6.0.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz",
 | 
			
		||||
      "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "ajv": "^6.10.2",
 | 
			
		||||
        "lodash": "^4.17.14",
 | 
			
		||||
        "slice-ansi": "^2.1.0",
 | 
			
		||||
        "string-width": "^3.0.0"
 | 
			
		||||
        "ajv": "^7.0.2",
 | 
			
		||||
        "lodash": "^4.17.20",
 | 
			
		||||
        "slice-ansi": "^4.0.0",
 | 
			
		||||
        "string-width": "^4.2.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "string-width": {
 | 
			
		||||
          "version": "3.1.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
 | 
			
		||||
          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
 | 
			
		||||
        "ajv": {
 | 
			
		||||
          "version": "7.2.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.2.1.tgz",
 | 
			
		||||
          "integrity": "sha512-+nu0HDv7kNSOua9apAVc979qd932rrZeb3WOvoiD31A/p1mIE5/9bN2027pE2rOPYEdS3UHzsvof4hY+lM9/WQ==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "emoji-regex": "^7.0.1",
 | 
			
		||||
            "is-fullwidth-code-point": "^2.0.0",
 | 
			
		||||
            "strip-ansi": "^5.1.0"
 | 
			
		||||
            "fast-deep-equal": "^3.1.1",
 | 
			
		||||
            "json-schema-traverse": "^1.0.0",
 | 
			
		||||
            "require-from-string": "^2.0.2",
 | 
			
		||||
            "uri-js": "^4.2.2"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "ansi-regex": {
 | 
			
		||||
          "version": "5.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "emoji-regex": {
 | 
			
		||||
          "version": "8.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "is-fullwidth-code-point": {
 | 
			
		||||
          "version": "3.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "json-schema-traverse": {
 | 
			
		||||
          "version": "1.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
 | 
			
		||||
          "dev": true
 | 
			
		||||
        },
 | 
			
		||||
        "string-width": {
 | 
			
		||||
          "version": "4.2.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz",
 | 
			
		||||
          "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "emoji-regex": "^8.0.0",
 | 
			
		||||
            "is-fullwidth-code-point": "^3.0.0",
 | 
			
		||||
            "strip-ansi": "^6.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "strip-ansi": {
 | 
			
		||||
          "version": "6.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
 | 
			
		||||
          "dev": true,
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "ansi-regex": "^5.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@ -21384,9 +21407,9 @@
 | 
			
		||||
      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
 | 
			
		||||
    },
 | 
			
		||||
    "v8-compile-cache": {
 | 
			
		||||
      "version": "2.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
 | 
			
		||||
      "version": "2.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "v8-to-istanbul": {
 | 
			
		||||
@ -22850,15 +22873,6 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
 | 
			
		||||
    },
 | 
			
		||||
    "write": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "mkdirp": "^0.5.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "write-file-atomic": {
 | 
			
		||||
      "version": "3.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@
 | 
			
		||||
    "test:ci": "NODE_ENV=testing gulp && jest -ci --runInBand --verbose",
 | 
			
		||||
    "test:watch": "NODE_ENV=testing gulp watch & jest --watch",
 | 
			
		||||
    "test:coverage": "NODE_ENV=testing gulp && jest --coverage",
 | 
			
		||||
    "lint": "ng lint",
 | 
			
		||||
    "lint": "NODE_OPTIONS=--max-old-space-size=4096 ng lint",
 | 
			
		||||
    "ionic:serve:before": "gulp",
 | 
			
		||||
    "ionic:serve": "gulp watch & ng serve",
 | 
			
		||||
    "ionic:build:before": "gulp"
 | 
			
		||||
@ -138,7 +138,7 @@
 | 
			
		||||
    "@types/webpack-env": "^1.16.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "4.3.0",
 | 
			
		||||
    "@typescript-eslint/parser": "4.3.0",
 | 
			
		||||
    "eslint": "^7.6.0",
 | 
			
		||||
    "eslint": "^7.21.0",
 | 
			
		||||
    "eslint-config-prettier": "^6.12.0",
 | 
			
		||||
    "eslint-plugin-header": "^3.1.0",
 | 
			
		||||
    "eslint-plugin-import": "^2.22.1",
 | 
			
		||||
 | 
			
		||||
@ -26,11 +26,13 @@ import { AddonMessagesModule } from './messages/messages.module';
 | 
			
		||||
import { AddonModModule } from './mod/mod.module';
 | 
			
		||||
import { AddonQbehaviourModule } from './qbehaviour/qbehaviour.module';
 | 
			
		||||
import { AddonQtypeModule } from './qtype/qtype.module';
 | 
			
		||||
import { AddonBlogModule } from './blog/blog.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        AddonBlockModule,
 | 
			
		||||
        AddonBadgesModule,
 | 
			
		||||
        AddonBlogModule,
 | 
			
		||||
        AddonCalendarModule,
 | 
			
		||||
        AddonMessagesModule,
 | 
			
		||||
        AddonPrivateFilesModule,
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,6 @@ import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonBadges } from '../badges';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to user participants page.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -41,7 +41,6 @@ export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseCompo
 | 
			
		||||
        super('AddonBlockRecentlyAccessedItemsComponent');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,6 @@ export class AddonBlockRecentlyAccessedItemsProvider {
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlockRecentlyAccessedItems = makeSingleton(AddonBlockRecentlyAccessedItemsProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Result of WS block_recentlyaccesseditems_get_recent_items.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,6 @@ import { CoreCourseComponentsModule } from '@features/course/components/componen
 | 
			
		||||
 | 
			
		||||
import { AddonBlockSiteMainMenuComponent } from './sitemainmenu/sitemainmenu';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonBlockSiteMainMenuComponent,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										52
									
								
								src/addons/blog/blog-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/addons/blog/blog-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injector, NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, ROUTES, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { AddonBlogEntriesPage } from './pages/entries/entries';
 | 
			
		||||
import { CoreCommentsComponentsModule } from '@features/comments/components/components.module';
 | 
			
		||||
 | 
			
		||||
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
 | 
			
		||||
import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
 | 
			
		||||
function buildRoutes(injector: Injector): Routes {
 | 
			
		||||
    return [
 | 
			
		||||
        ...buildTabMainRoutes(injector, {
 | 
			
		||||
            component: AddonBlogEntriesPage,
 | 
			
		||||
        }),
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCommentsComponentsModule,
 | 
			
		||||
        CoreTagComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [RouterModule],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: ROUTES,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [Injector],
 | 
			
		||||
            useFactory: buildRoutes,
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonBlogEntriesPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlogLazyModule {}
 | 
			
		||||
							
								
								
									
										65
									
								
								src/addons/blog/blog.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/addons/blog/blog.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
 | 
			
		||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
 | 
			
		||||
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
 | 
			
		||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
 | 
			
		||||
import { AddonBlogProvider } from './services/blog';
 | 
			
		||||
import { AddonBlogCourseOptionHandler } from './services/handlers/course-option';
 | 
			
		||||
import { AddonBlogIndexLinkHandler } from './services/handlers/index-link';
 | 
			
		||||
import { AddonBlogMainMenuHandler, AddonBlogMainMenuHandlerService } from './services/handlers/mainmenu';
 | 
			
		||||
import { AddonBlogTagAreaHandler } from './services/handlers/tag-area';
 | 
			
		||||
import { AddonBlogUserHandler } from './services/handlers/user';
 | 
			
		||||
 | 
			
		||||
export const ADDON_BLOG_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    AddonBlogProvider,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: AddonBlogMainMenuHandlerService.PAGE_NAME,
 | 
			
		||||
        loadChildren: () => import('@addons/blog/blog-lazy.module').then(m => m.AddonBlogLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        CoreMainMenuRoutingModule.forChild({ children: routes }),
 | 
			
		||||
        CoreCourseIndexRoutingModule.forChild({ children: routes }),
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [CoreMainMenuRoutingModule],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [],
 | 
			
		||||
            useFactory: () => async () => {
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonBlogIndexLinkHandler.instance);
 | 
			
		||||
                CoreMainMenuDelegate.registerHandler(AddonBlogMainMenuHandler.instance);
 | 
			
		||||
                CoreUserDelegate.registerHandler(AddonBlogUserHandler.instance);
 | 
			
		||||
                CoreTagAreaDelegate.registerHandler(AddonBlogTagAreaHandler.instance);
 | 
			
		||||
                CoreCourseOptionsDelegate.registerHandler(AddonBlogCourseOptionHandler.instance);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlogModule {}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/addons/blog/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/addons/blog/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
{
 | 
			
		||||
    "blog": "Blog",
 | 
			
		||||
    "blogentries": "Blog entries",
 | 
			
		||||
    "errorloadentries": "Error loading blog entries.",
 | 
			
		||||
    "linktooriginalentry": "Link to original blog entry",
 | 
			
		||||
    "noentriesyet": "No visible entries here",
 | 
			
		||||
    "publishtonoone": "Yourself (draft)",
 | 
			
		||||
    "publishtosite": "Anyone on this site",
 | 
			
		||||
    "publishtoworld": "Anyone in the world",
 | 
			
		||||
    "showonlyyourentries": "Show only your entries",
 | 
			
		||||
    "siteblogheading": "Site blog"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								src/addons/blog/pages/entries/entries.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/addons/blog/pages/entries/entries.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>{{ title | translate }}</ion-title>
 | 
			
		||||
        <ion-buttons slot="end"></ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refresh($event)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
    <core-loading [hideUntil]="loaded" class="core-loading-center">
 | 
			
		||||
        <ion-item *ngIf="showMyEntriesToggle">
 | 
			
		||||
            <ion-label>{{ 'addon.blog.showonlyyourentries' | translate }}</ion-label>
 | 
			
		||||
            <ion-toggle [(ngModel)]="onlyMyEntries" (ionChange)="onlyMyEntriesToggleChanged(onlyMyEntries)"></ion-toggle>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
        <core-empty-box *ngIf="entries && entries.length == 0" icon="far-newspaper"
 | 
			
		||||
            [message]="'addon.blog.noentriesyet' | translate">
 | 
			
		||||
        </core-empty-box>
 | 
			
		||||
        <ng-container *ngFor="let entry of entries">
 | 
			
		||||
            <ion-card *ngIf="!onlyMyEntries || entry.userid == currentUserId">
 | 
			
		||||
                <ion-item class="ion-text-wrap">
 | 
			
		||||
                    <core-user-avatar [user]="entry.user" slot="start" [courseId]="entry.courseid"></core-user-avatar>
 | 
			
		||||
                    <ion-label>
 | 
			
		||||
                        <h2>
 | 
			
		||||
                            <core-format-text [text]="entry.subject" [contextLevel]="contextLevel"
 | 
			
		||||
                                [contextInstanceId]="contextInstanceId">
 | 
			
		||||
                            </core-format-text>
 | 
			
		||||
                            <ion-note class="ion-float-end ion-padding-left ion-text-end">
 | 
			
		||||
                                {{ 'addon.blog.' + entry.publishTranslated! | translate}}
 | 
			
		||||
                            </ion-note>
 | 
			
		||||
                        </h2>
 | 
			
		||||
                        <p>
 | 
			
		||||
                            <ion-note class="ion-float-end ion-padding-left ion-text-end">
 | 
			
		||||
                                {{entry.created | coreDateDayOrTime}}
 | 
			
		||||
                            </ion-note>
 | 
			
		||||
                            {{entry.user && entry.user!.fullname}}
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </ion-label>
 | 
			
		||||
                </ion-item>
 | 
			
		||||
                <ion-card-content>
 | 
			
		||||
                    <ion-item>
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <core-format-text [text]="entry.summary" [component]="this.component" [componentId]="entry.id"
 | 
			
		||||
                                [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId">
 | 
			
		||||
                            </core-format-text>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                    <ion-item class="ion-text-wrap" *ngIf="tagsEnabled && entry.tags && entry.tags!.length > 0">
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <div slot="start">{{ 'core.tag.tags' | translate }}:</div>
 | 
			
		||||
                            <core-tag-list [tags]="entry.tags"></core-tag-list>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                    <ion-item *ngIf="commentsEnabled" detail>
 | 
			
		||||
                        <ion-label>
 | 
			
		||||
                            <core-comments [component]="this.component" [itemId]="entry.id" area="format_blog"
 | 
			
		||||
                                [instanceId]="entry.userid" contextLevel="user">
 | 
			
		||||
                            </core-comments>
 | 
			
		||||
                        </ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                    <core-file *ngFor="let file of entry.attachmentfiles" [file]="file" [component]="this.component"
 | 
			
		||||
                        [componentId]="entry.id">
 | 
			
		||||
                    </core-file>
 | 
			
		||||
                    <ion-item *ngIf="entry.uniquehash" [href]="entry.uniquehash" core-link>
 | 
			
		||||
                        <ion-label>{{ 'addon.blog.linktooriginalentry' | translate }}</ion-label>
 | 
			
		||||
                    </ion-item>
 | 
			
		||||
                </ion-card-content>
 | 
			
		||||
                <ion-row class="ion-text-center">
 | 
			
		||||
                    <ion-col *ngIf="entry.lastmodified > entry.created">
 | 
			
		||||
                        <ion-note>
 | 
			
		||||
                            <ion-icon name="fas-clock"></ion-icon> {{entry.lastmodified | coreTimeAgo}}
 | 
			
		||||
                        </ion-note>
 | 
			
		||||
                    </ion-col>
 | 
			
		||||
                </ion-row>
 | 
			
		||||
            </ion-card>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
        <core-infinite-loading [enabled]="canLoadMore" (action)="loadMore($event)" [error]="loadMoreError"></core-infinite-loading>
 | 
			
		||||
    </core-loading>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										288
									
								
								src/addons/blog/pages/entries/entries.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								src/addons/blog/pages/entries/entries.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,288 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { ContextLevel } from '@/core/constants';
 | 
			
		||||
import { AddonBlog, AddonBlogFilter, AddonBlogPost, AddonBlogProvider } from '@addons/blog/services/blog';
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { CoreComments } from '@features/comments/services/comments';
 | 
			
		||||
import { CoreTag } from '@features/tag/services/tag';
 | 
			
		||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
 | 
			
		||||
import { IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the list of blog entries.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-blog-entries',
 | 
			
		||||
    templateUrl: 'entries.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonBlogEntriesPage implements OnInit {
 | 
			
		||||
 | 
			
		||||
    title = '';
 | 
			
		||||
 | 
			
		||||
    protected filter: AddonBlogFilter = {};
 | 
			
		||||
    protected pageLoaded = 0;
 | 
			
		||||
    protected userPageLoaded = 0;
 | 
			
		||||
    protected canLoadMoreEntries = false;
 | 
			
		||||
    protected canLoadMoreUserEntries = true;
 | 
			
		||||
    protected siteHomeId: number;
 | 
			
		||||
 | 
			
		||||
    loaded = false;
 | 
			
		||||
    canLoadMore = false;
 | 
			
		||||
    loadMoreError = false;
 | 
			
		||||
    entries: AddonBlogPostFormatted[] = [];
 | 
			
		||||
    currentUserId: number;
 | 
			
		||||
    showMyEntriesToggle = false;
 | 
			
		||||
    onlyMyEntries = false;
 | 
			
		||||
    component = AddonBlogProvider.COMPONENT;
 | 
			
		||||
    commentsEnabled = false;
 | 
			
		||||
    tagsEnabled = false;
 | 
			
		||||
    contextLevel: ContextLevel = ContextLevel.SYSTEM;
 | 
			
		||||
    contextInstanceId = 0;
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.currentUserId = CoreSites.getCurrentSiteUserId();
 | 
			
		||||
        this.siteHomeId = CoreSites.getCurrentSiteHomeId();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * View loaded.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        const userId = CoreNavigator.getRouteNumberParam('userId');
 | 
			
		||||
        const courseId = CoreNavigator.getRouteNumberParam('courseId');
 | 
			
		||||
        const cmId = CoreNavigator.getRouteNumberParam('cmId');
 | 
			
		||||
        const entryId = CoreNavigator.getRouteNumberParam('entryId');
 | 
			
		||||
        const groupId = CoreNavigator.getRouteNumberParam('groupId');
 | 
			
		||||
        const tagId = CoreNavigator.getRouteNumberParam('tagId');
 | 
			
		||||
 | 
			
		||||
        if (!userId && !courseId && !cmId && !entryId && !groupId && !tagId) {
 | 
			
		||||
            this.title = 'addon.blog.siteblogheading';
 | 
			
		||||
        } else {
 | 
			
		||||
            this.title = 'addon.blog.blogentries';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (userId) {
 | 
			
		||||
            this.filter.userid = userId;
 | 
			
		||||
        }
 | 
			
		||||
        this.showMyEntriesToggle = !userId;
 | 
			
		||||
 | 
			
		||||
        if (courseId) {
 | 
			
		||||
            this.filter.courseid = courseId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (cmId) {
 | 
			
		||||
            this.filter.cmid = cmId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (entryId) {
 | 
			
		||||
            this.filter.entryid = entryId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (groupId) {
 | 
			
		||||
            this.filter.groupid = groupId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (tagId) {
 | 
			
		||||
            this.filter.tagid = tagId;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Calculate the context level.
 | 
			
		||||
        if (userId && !courseId && !cmId) {
 | 
			
		||||
            this.contextLevel = ContextLevel.USER;
 | 
			
		||||
            this.contextInstanceId = userId;
 | 
			
		||||
        } else if (courseId && courseId != this.siteHomeId) {
 | 
			
		||||
            this.contextLevel = ContextLevel.COURSE;
 | 
			
		||||
            this.contextInstanceId = courseId;
 | 
			
		||||
        } else {
 | 
			
		||||
            this.contextLevel = ContextLevel.SYSTEM;
 | 
			
		||||
            this.contextInstanceId = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.commentsEnabled = !CoreComments.areCommentsDisabledInSite();
 | 
			
		||||
        this.tagsEnabled = CoreTag.areTagsAvailableInSite();
 | 
			
		||||
 | 
			
		||||
        await this.fetchEntries();
 | 
			
		||||
 | 
			
		||||
        CoreUtils.ignoreErrors(AddonBlog.logView(this.filter));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fetch blog entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Empty events array first.
 | 
			
		||||
     * @return Promise with the entries.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchEntries(refresh: boolean = false): Promise<void> {
 | 
			
		||||
        this.loadMoreError = false;
 | 
			
		||||
 | 
			
		||||
        if (refresh) {
 | 
			
		||||
            this.pageLoaded = 0;
 | 
			
		||||
            this.userPageLoaded = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await AddonBlog.getEntries(this.filter, loadPage);
 | 
			
		||||
 | 
			
		||||
            const promises = result.entries.map(async (entry: AddonBlogPostFormatted) => {
 | 
			
		||||
                switch (entry.publishstate) {
 | 
			
		||||
                    case 'draft':
 | 
			
		||||
                        entry.publishTranslated = 'publishtonoone';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'site':
 | 
			
		||||
                        entry.publishTranslated = 'publishtosite';
 | 
			
		||||
                        break;
 | 
			
		||||
                    case 'public':
 | 
			
		||||
                        entry.publishTranslated = 'publishtoworld';
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        entry.publishTranslated = 'privacy:unknown';
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Calculate the context. This code was inspired by calendar events, Moodle doesn't do this for blogs.
 | 
			
		||||
                if (entry.moduleid || entry.coursemoduleid) {
 | 
			
		||||
                    entry.contextLevel = ContextLevel.MODULE;
 | 
			
		||||
                    entry.contextInstanceId = entry.moduleid || entry.coursemoduleid;
 | 
			
		||||
                } else if (entry.courseid) {
 | 
			
		||||
                    entry.contextLevel = ContextLevel.COURSE;
 | 
			
		||||
                    entry.contextInstanceId = entry.courseid;
 | 
			
		||||
                } else {
 | 
			
		||||
                    entry.contextLevel = ContextLevel.USER;
 | 
			
		||||
                    entry.contextInstanceId = entry.userid;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                entry.summary = CoreTextUtils.instance.replacePluginfileUrls(entry.summary, entry.summaryfiles || []);
 | 
			
		||||
 | 
			
		||||
                return CoreUser.getProfile(entry.userid, entry.courseid, true).then((user) => {
 | 
			
		||||
                    entry.user = user;
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }).catch(() => {
 | 
			
		||||
                    // Ignore errors.
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (refresh) {
 | 
			
		||||
                this.entries = result.entries;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.entries = CoreUtils.uniqueArray(this.entries
 | 
			
		||||
                    .concat(result.entries), 'id')
 | 
			
		||||
                    .sort((a, b) => b.created - a.created);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (this.onlyMyEntries) {
 | 
			
		||||
                const count = this.entries.filter((entry) => entry.userid == this.currentUserId).length;
 | 
			
		||||
                this.canLoadMoreUserEntries = result.totalentries > count;
 | 
			
		||||
                this.canLoadMore = this.canLoadMoreUserEntries;
 | 
			
		||||
                this.userPageLoaded++;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.canLoadMoreEntries = result.totalentries > this.entries.length;
 | 
			
		||||
                this.canLoadMore = this.canLoadMoreEntries;
 | 
			
		||||
                this.pageLoaded++;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Promise.all(promises);
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.blog.errorloadentries', true);
 | 
			
		||||
            this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading.
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.loaded = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle between showing only my entries or not.
 | 
			
		||||
     *
 | 
			
		||||
     * @param enabled If true, filter my entries. False otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    onlyMyEntriesToggleChanged(enabled: boolean): void {
 | 
			
		||||
        this.canLoadMore = enabled ? this.canLoadMoreUserEntries : this.canLoadMoreEntries;
 | 
			
		||||
 | 
			
		||||
        if (!enabled) {
 | 
			
		||||
            delete this.filter.userid;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const count = this.entries.filter((entry) => entry.userid == this.currentUserId).length;
 | 
			
		||||
        this.userPageLoaded = Math.floor(count / AddonBlogProvider.ENTRIES_PER_PAGE);
 | 
			
		||||
        this.filter.userid = this.currentUserId;
 | 
			
		||||
 | 
			
		||||
        if (count == 0 && this.canLoadMoreUserEntries) {
 | 
			
		||||
            // First time but no entry loaded. Try to load some.
 | 
			
		||||
            this.loadMore();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Function to load more entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    loadMore(infiniteComplete?: () => void): Promise<void> {
 | 
			
		||||
        return this.fetchEntries().finally(() => {
 | 
			
		||||
            infiniteComplete && infiniteComplete();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh blog entries on PTR.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresher Refresher instance.
 | 
			
		||||
     */
 | 
			
		||||
    refresh(refresher?: CustomEvent<IonRefresher>): void {
 | 
			
		||||
        const promises = this.entries.map((entry) =>
 | 
			
		||||
            CoreComments.invalidateCommentsData('user', entry.userid, this.component, entry.id, 'format_blog'));
 | 
			
		||||
 | 
			
		||||
        promises.push(AddonBlog.invalidateEntries(this.filter));
 | 
			
		||||
 | 
			
		||||
        if (this.showMyEntriesToggle) {
 | 
			
		||||
            this.filter['userid'] = this.currentUserId;
 | 
			
		||||
            promises.push(AddonBlog.invalidateEntries(this.filter));
 | 
			
		||||
 | 
			
		||||
            if (!this.onlyMyEntries) {
 | 
			
		||||
                delete this.filter['userid'];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CoreUtils.allPromises(promises).finally(() => {
 | 
			
		||||
            this.fetchEntries(true).finally(() => {
 | 
			
		||||
                if (refresher) {
 | 
			
		||||
                    refresher?.detail.complete();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Blog post with some calculated data.
 | 
			
		||||
 */
 | 
			
		||||
type AddonBlogPostFormatted = AddonBlogPost & {
 | 
			
		||||
    publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post.
 | 
			
		||||
    user?: CoreUserProfile; // Calculated in the app. Data of the user that wrote the post.
 | 
			
		||||
    contextLevel?: string; // Calculated in the app. The context level of the entry.
 | 
			
		||||
    contextInstanceId?: number; // Calculated in the app. The context instance id.
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										204
									
								
								src/addons/blog/services/blog.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										204
									
								
								src/addons/blog/services/blog.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,204 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
			
		||||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
 | 
			
		||||
import { CoreTagItem } from '@features/tag/services/tag';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'addonBlog:';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to handle blog entries.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonBlogProvider {
 | 
			
		||||
 | 
			
		||||
    static readonly ENTRIES_PER_PAGE = 10;
 | 
			
		||||
    static readonly COMPONENT = 'blog';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether or not the blog plugin is enabled for a certain site.
 | 
			
		||||
     *
 | 
			
		||||
     * This method is called quite often and thus should only perform a quick
 | 
			
		||||
     * check, we should not be calling WS from here.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if enabled, resolved with false or rejected otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async isPluginEnabled(siteId?: string): Promise<boolean> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.wsAvailable('core_blog_get_entries') &&site.canUseAdvancedFeature('enableblogs');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the cache key for the blog entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filter Filter to apply on search.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    getEntriesCacheKey(filter: AddonBlogFilter = {}): string {
 | 
			
		||||
        return ROOT_CACHE_KEY + CoreUtils.sortAndStringify(filter);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get blog entries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filter Filter to apply on search.
 | 
			
		||||
     * @param page Page of the blog entries to fetch.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise to be resolved when the entries are retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    async getEntries(filter: AddonBlogFilter = {}, page: number = 0, siteId?: string): Promise<CoreBlogGetEntriesWSResponse> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const data: CoreBlogGetEntriesWSParams = {
 | 
			
		||||
            filters: CoreUtils.objectToArrayOfObjects(filter, 'name', 'value'),
 | 
			
		||||
            page: page,
 | 
			
		||||
            perpage: AddonBlogProvider.ENTRIES_PER_PAGE,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            cacheKey: this.getEntriesCacheKey(filter),
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_SOMETIMES,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return site.read('core_blog_get_entries', data, preSets);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate blog entries WS call.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filter Filter to apply on search
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateEntries(filter: AddonBlogFilter = {}, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKey(this.getEntriesCacheKey(filter));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Trigger the blog_entries_viewed event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param filter Filter to apply on search.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise to be resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async logView(filter: AddonBlogFilter = {}, siteId?: string): Promise<CoreStatusWithWarningsWSResponse> {
 | 
			
		||||
        CorePushNotifications.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId);
 | 
			
		||||
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        const data: AddonBlogViewEntriesWSParams = {
 | 
			
		||||
            filters: CoreUtils.objectToArrayOfObjects(filter, 'name', 'value'),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return site.write('core_blog_view_entries', data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlog = makeSingleton(AddonBlogProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_blog_get_entries WS.
 | 
			
		||||
 */
 | 
			
		||||
type CoreBlogGetEntriesWSParams = {
 | 
			
		||||
    filters?: { // Parameters to filter blog listings.
 | 
			
		||||
        name: string; // The expected keys (value format) are:
 | 
			
		||||
        // tag      PARAM_NOTAGS blog tag
 | 
			
		||||
        // tagid    PARAM_INT    blog tag id
 | 
			
		||||
        // userid   PARAM_INT    blog author (userid)
 | 
			
		||||
        // cmid    PARAM_INT    course module id
 | 
			
		||||
        // entryid  PARAM_INT    entry id
 | 
			
		||||
        // groupid  PARAM_INT    group id
 | 
			
		||||
        // courseid PARAM_INT    course id
 | 
			
		||||
        // search   PARAM_RAW    search term.
 | 
			
		||||
        value: string; // The value of the filter.
 | 
			
		||||
    }[];
 | 
			
		||||
    page?: number; // The blog page to return.
 | 
			
		||||
    perpage?: number; // The number of posts to return per page.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by core_blog_get_entries WS.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreBlogGetEntriesWSResponse = {
 | 
			
		||||
    entries: AddonBlogPost[];
 | 
			
		||||
    totalentries: number; // The total number of entries found.
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by blog's post_exporter.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonBlogPost = {
 | 
			
		||||
    id: number; // Post/entry id.
 | 
			
		||||
    module: string; // Where it was published the post (blog, blog_external...).
 | 
			
		||||
    userid: number; // Post author.
 | 
			
		||||
    courseid: number; // Course where the post was created.
 | 
			
		||||
    groupid: number; // Group post was created for.
 | 
			
		||||
    moduleid: number; // Module id where the post was created (not used anymore).
 | 
			
		||||
    coursemoduleid: number; // Course module id where the post was created.
 | 
			
		||||
    subject: string; // Post subject.
 | 
			
		||||
    summary: string; // Post summary.
 | 
			
		||||
    summaryformat?: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | 
			
		||||
    content: string; // Post content.
 | 
			
		||||
    uniquehash: string; // Post unique hash.
 | 
			
		||||
    rating: number; // Post rating.
 | 
			
		||||
    format: number; // Post content format.
 | 
			
		||||
    attachment: string; // Post atachment.
 | 
			
		||||
    publishstate: string; // Post publish state.
 | 
			
		||||
    lastmodified: number; // When it was last modified.
 | 
			
		||||
    created: number; // When it was created.
 | 
			
		||||
    usermodified: number; // User that updated the post.
 | 
			
		||||
    summaryfiles: CoreWSExternalFile[]; // Summaryfiles.
 | 
			
		||||
    attachmentfiles?: CoreWSExternalFile[]; // Attachmentfiles.
 | 
			
		||||
    tags?: CoreTagItem[]; // @since 3.7. Tags.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_blog_view_entries WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonBlogViewEntriesWSParams = {
 | 
			
		||||
    filters?: { // Parameters used in the filter of view_entries.
 | 
			
		||||
        name: string; // The expected keys (value format) are:
 | 
			
		||||
        // tag      PARAM_NOTAGS blog tag
 | 
			
		||||
        // tagid    PARAM_INT    blog tag id
 | 
			
		||||
        // userid   PARAM_INT    blog author (userid)
 | 
			
		||||
        // cmid     PARAM_INT    course module id
 | 
			
		||||
        // entryid  PARAM_INT    entry id
 | 
			
		||||
        // groupid  PARAM_INT    group id
 | 
			
		||||
        // courseid PARAM_INT    course id
 | 
			
		||||
        // search   PARAM_RAW    search term.
 | 
			
		||||
        value: string; // The value of the filter.
 | 
			
		||||
    }[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonBlogFilter = {
 | 
			
		||||
    tag?: string;      // Blog tag
 | 
			
		||||
    tagid?: number;    // Blog tag id
 | 
			
		||||
    userid?: number;   // Blog author (userid)
 | 
			
		||||
    cmid?: number;     // Course module id
 | 
			
		||||
    entryid?: number;  // Entry id
 | 
			
		||||
    groupid?: number;  // Group id
 | 
			
		||||
    courseid?: number; // Course id
 | 
			
		||||
    search?: string;   // Search term.
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										109
									
								
								src/addons/blog/services/handlers/course-option.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/addons/blog/services/handlers/course-option.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseAccess,
 | 
			
		||||
    CoreCourseOptionsHandler,
 | 
			
		||||
    CoreCourseOptionsHandlerData,
 | 
			
		||||
} from '@features/course/services/course-options-delegate';
 | 
			
		||||
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
 | 
			
		||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonBlog } from '../blog';
 | 
			
		||||
import { AddonBlogMainMenuHandlerService } from './mainmenu';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Course nav handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonBlogCourseOptionHandlerService implements CoreCourseOptionsHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonBlog';
 | 
			
		||||
    priority = 100;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    invalidateEnabledForCourse(courseId: number): Promise<void> {
 | 
			
		||||
        return CoreCourse.invalidateCourseBlocks(courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonBlog.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabledForCourse(
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        accessData: CoreCourseAccess,
 | 
			
		||||
        navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
 | 
			
		||||
    ): Promise<boolean> {
 | 
			
		||||
        const enabled = await CoreCourseHelper.hasABlockNamed(courseId, 'blog_menu');
 | 
			
		||||
 | 
			
		||||
        if (enabled && navOptions && typeof navOptions.blogs != 'undefined') {
 | 
			
		||||
            return navOptions.blogs;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
 | 
			
		||||
        return {
 | 
			
		||||
            title: 'addon.blog.blog',
 | 
			
		||||
            class: 'addon-blog-handler',
 | 
			
		||||
            page: AddonBlogMainMenuHandlerService.PAGE_NAME,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
 | 
			
		||||
        const siteId = CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const result = await AddonBlog.getEntries({ courseid: course.id });
 | 
			
		||||
 | 
			
		||||
        await Promise.all(result.entries.map(async (entry) => {
 | 
			
		||||
            let files: CoreWSExternalFile[] = [];
 | 
			
		||||
 | 
			
		||||
            if (entry.attachmentfiles && entry.attachmentfiles.length) {
 | 
			
		||||
                files = entry.attachmentfiles;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (entry.summaryfiles && entry.summaryfiles.length) {
 | 
			
		||||
                files = files.concat(entry.summaryfiles);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (files.length > 0) {
 | 
			
		||||
                await CoreFilepool.addFilesToQueue(siteId, files, entry.module, entry.id);
 | 
			
		||||
            }
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlogCourseOptionHandler = makeSingleton(AddonBlogCourseOptionHandlerService);
 | 
			
		||||
							
								
								
									
										61
									
								
								src/addons/blog/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/addons/blog/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
 | 
			
		||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonBlog } from '../blog';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to blog page.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonBlogIndexLinkHandlerService extends CoreContentLinksHandlerBase {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonBlogIndexLinkHandler';
 | 
			
		||||
    featureName = 'CoreUserDelegate_AddonBlog:blogs';
 | 
			
		||||
    pattern = /\/blog\/index\.php/;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
 | 
			
		||||
        const pageParams: Params = {};
 | 
			
		||||
 | 
			
		||||
        params.userid ? pageParams['userId'] = parseInt(params.userid, 10) : null;
 | 
			
		||||
        params.modid ? pageParams['cmId'] = parseInt(params.modid, 10) : null;
 | 
			
		||||
        params.courseid ? pageParams['courseId'] = parseInt(params.courseid, 10) : null;
 | 
			
		||||
        params.entryid ? pageParams['entryId'] = parseInt(params.entryid, 10) : null;
 | 
			
		||||
        params.groupid ? pageParams['groupId'] = parseInt(params.groupid, 10) : null;
 | 
			
		||||
        params.tagid ? pageParams['tagId'] = parseInt(params.tagid, 10) : null;
 | 
			
		||||
 | 
			
		||||
        return [{
 | 
			
		||||
            action: (siteId: string): void => {
 | 
			
		||||
                CoreNavigator.navigateToSitePath('/blog', { params: pageParams, siteId });
 | 
			
		||||
            },
 | 
			
		||||
        }];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(siteId: string): Promise<boolean> {
 | 
			
		||||
        return AddonBlog.isPluginEnabled(siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlogIndexLinkHandler = makeSingleton(AddonBlogIndexLinkHandlerService);
 | 
			
		||||
							
								
								
									
										51
									
								
								src/addons/blog/services/handlers/mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/addons/blog/services/handlers/mainmenu.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonBlog } from '../blog';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to inject an option into main menu.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonBlogMainMenuHandlerService implements CoreMainMenuHandler {
 | 
			
		||||
 | 
			
		||||
    static readonly PAGE_NAME = 'blog';
 | 
			
		||||
 | 
			
		||||
    name = 'AddonBlog';
 | 
			
		||||
    priority = 450;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonBlog.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreMainMenuHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
            icon: 'far-newspaper',
 | 
			
		||||
            title: 'addon.blog.siteblogheading',
 | 
			
		||||
            page: AddonBlogMainMenuHandlerService.PAGE_NAME,
 | 
			
		||||
            class: 'addon-blog-handler',
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlogMainMenuHandler = makeSingleton(AddonBlogMainMenuHandlerService);
 | 
			
		||||
							
								
								
									
										53
									
								
								src/addons/blog/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/addons/blog/services/handlers/tag-area.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,53 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable, Type } from '@angular/core';
 | 
			
		||||
import { CoreTagFeedComponent } from '@features/tag/components/feed/feed';
 | 
			
		||||
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
 | 
			
		||||
import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonBlog } from '../blog';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to support tags.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonBlogTagAreaHandlerService implements CoreTagAreaHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonBlogTagAreaHandler';
 | 
			
		||||
    type = 'core/post';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonBlog.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async parseContent(content: string): Promise<CoreTagFeedElement[]> {
 | 
			
		||||
        return CoreTagHelper.parseFeedContent(content);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getComponent(): Type<unknown> | Promise<Type<unknown>> {
 | 
			
		||||
        return CoreTagFeedComponent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlogTagAreaHandler = makeSingleton(AddonBlogTagAreaHandlerService);
 | 
			
		||||
							
								
								
									
										64
									
								
								src/addons/blog/services/handlers/user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/addons/blog/services/handlers/user.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
			
		||||
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
			
		||||
//
 | 
			
		||||
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
// you may not use this file except in compliance with the License.
 | 
			
		||||
// You may obtain a copy of the License at
 | 
			
		||||
//
 | 
			
		||||
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
			
		||||
//
 | 
			
		||||
// Unless required by applicable law or agreed to in writing, software
 | 
			
		||||
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
			
		||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreUserProfileHandler, CoreUserProfileHandlerData, CoreUserDelegateService } from '@features/user/services/user-delegate';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonBlog } from '../blog';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Profile item handler.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonBlogUserHandlerService implements CoreUserProfileHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonBlog:blogs';
 | 
			
		||||
    priority = 300;
 | 
			
		||||
    type = CoreUserDelegateService.TYPE_NEW_PAGE;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonBlog.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabledForUser(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getDisplayData(): CoreUserProfileHandlerData {
 | 
			
		||||
        return {
 | 
			
		||||
            icon: 'far-newspaper',
 | 
			
		||||
            title: 'addon.blog.blogentries',
 | 
			
		||||
            class: 'addon-blog-handler',
 | 
			
		||||
            action: (event, user, courseId): void => {
 | 
			
		||||
                event.preventDefault();
 | 
			
		||||
                event.stopPropagation();
 | 
			
		||||
                CoreNavigator.navigateToSitePath('/blog', {
 | 
			
		||||
                    params: { courseId, userId: user.id },
 | 
			
		||||
                });
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonBlogUserHandler = makeSingleton(AddonBlogUserHandlerService);
 | 
			
		||||
@ -10,6 +10,7 @@
 | 
			
		||||
    .addon-calendar-months {
 | 
			
		||||
        background-color: var(--contrast-background);
 | 
			
		||||
        padding: 0;
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .addon-calendar-day {
 | 
			
		||||
 | 
			
		||||
@ -139,7 +139,6 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
 | 
			
		||||
        this.fetchData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays).
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ import { AddonCalendarFilter, AddonCalendarEventIcons } from '../../services/cal
 | 
			
		||||
})
 | 
			
		||||
export class AddonCalendarFilterPopoverComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Input() filter: AddonCalendarFilter = {
 | 
			
		||||
        filtered: false,
 | 
			
		||||
        courseId: -1,
 | 
			
		||||
@ -54,7 +53,6 @@ export class AddonCalendarFilterPopoverComponent implements OnInit {
 | 
			
		||||
            this.types.push(value);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -266,7 +266,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
                    // Ignore errors.
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (this.showAll) {
 | 
			
		||||
            // Remove site home from the list of courses.
 | 
			
		||||
            const siteHomeId = CoreSites.getCurrentSiteHomeId();
 | 
			
		||||
@ -285,7 +284,6 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave {
 | 
			
		||||
            await Promise.all((courses as CoreEnrolledCourseData[]).map(courseFillterFullname));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Sort courses by name.
 | 
			
		||||
        this.courses = courses.sort((a, b) => {
 | 
			
		||||
            const compareA = a.fullname.toLowerCase();
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,6 @@ import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreLocalNotifications } from '@services/local-notifications';
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays the calendar events.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,6 @@ const routes: Routes = [
 | 
			
		||||
    ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -55,7 +55,6 @@ export class AddonCalendarHelperProvider {
 | 
			
		||||
 | 
			
		||||
    protected eventTypeIcons: string[] = [];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns event icon based on event type.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -722,7 +722,6 @@ export class AddonCalendarProvider {
 | 
			
		||||
        const originalEvent = record as AddonCalendarGetEventsEvent;
 | 
			
		||||
        const recordAsRecord = record as AddonCalendarEventDBRecord;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Calculate data to match the new WS.
 | 
			
		||||
        eventConverted.descriptionformat = originalEvent.format;
 | 
			
		||||
        eventConverted.iscourseevent = originalEvent.eventtype == AddonCalendarEventType.COURSE;
 | 
			
		||||
@ -947,7 +946,6 @@ export class AddonCalendarProvider {
 | 
			
		||||
            params.events!.courseids = courses.map((course) => course.id);
 | 
			
		||||
            params.events!.courseids.push(site.getSiteHomeId()); // Add front page.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
@ -1941,7 +1939,6 @@ export type AddonCalendarCalendarDay = {
 | 
			
		||||
    rarrow: string; // Rarrow.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_calendar_get_calendar_monthly_view WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -2035,7 +2032,6 @@ export type AddonCalendarDayName = {
 | 
			
		||||
    fullname: string; // Fullname.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_calendar_get_calendar_upcoming_view WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -2090,7 +2086,6 @@ export type AddonCalendarGetAllowedEventTypesWSResponse = {
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_calendar_get_calendar_events WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -2187,7 +2182,6 @@ type AddonCalendarGetCalendarDayViewWSParams = {
 | 
			
		||||
    categoryid?: number; // Category being viewed.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_calendar_submit_create_update_form WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@ export class AddonCalendarMainMenuHandlerService implements CoreMainMenuHandler
 | 
			
		||||
 | 
			
		||||
    static readonly PAGE_NAME = 'calendar';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    name = 'AddonCalendar';
 | 
			
		||||
    priority = 900;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@ import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
 | 
			
		||||
import { AddonMessagesConversationInfoComponent } from './conversation-info/conversation-info';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonMessagesConversationInfoComponent,
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,12 @@
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .note {
 | 
			
		||||
            position: absolute;
 | 
			
		||||
            top: 0;
 | 
			
		||||
            right: 0;
 | 
			
		||||
            margin:  4px 8px;
 | 
			
		||||
            font-size: 1.3rem;
 | 
			
		||||
        ion-note {
 | 
			
		||||
            ion-badge {
 | 
			
		||||
                margin-left: 6px;
 | 
			
		||||
                margin-right: 6px;
 | 
			
		||||
                vertical-align: middle;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .addon-message-last-message {
 | 
			
		||||
@ -43,6 +43,11 @@
 | 
			
		||||
            margin-top: 10px;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ion-item-divider ion-badge {
 | 
			
		||||
        margin-left: 16px;
 | 
			
		||||
        margin-right: 16px;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:host-context([dir=rtl]) {
 | 
			
		||||
@ -53,11 +58,6 @@
 | 
			
		||||
            margin-left: 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .note {
 | 
			
		||||
            left: 0;
 | 
			
		||||
            right: unset;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .addon-message-last-message-user {
 | 
			
		||||
            margin-left: 2px;
 | 
			
		||||
            margin-right: 0;
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,6 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
 | 
			
		||||
 | 
			
		||||
    searchString = '';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected route: ActivatedRoute,
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
@ -45,7 +45,6 @@ const routes: Routes = [
 | 
			
		||||
    ...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
 | 
			
		||||
@ -58,7 +58,6 @@ export class AddonMessagesContactsPage implements OnInit, OnDestroy {
 | 
			
		||||
    protected contactRequestsCountObserver: CoreEventObserver;
 | 
			
		||||
    protected memberInfoObserver: CoreEventObserver;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
 | 
			
		||||
        this.siteId = CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
@ -326,7 +326,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
 | 
			
		||||
                        return;
 | 
			
		||||
                    }));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Fetch the messages for the first time.
 | 
			
		||||
@ -596,7 +595,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Retrieve the conversation. Invalidate data first to get the right unreadcount.
 | 
			
		||||
        await AddonMessages.invalidateConversation(conversationId!);
 | 
			
		||||
 | 
			
		||||
@ -1191,7 +1189,6 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
 | 
			
		||||
                    data = await AddonMessages.sendMessage(this.userId!, text);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                this.messagesBeingSent--;
 | 
			
		||||
                let failure = false;
 | 
			
		||||
                if (data.sent) {
 | 
			
		||||
 | 
			
		||||
@ -124,7 +124,6 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
 | 
			
		||||
            this.refreshData();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // If a message push notification is received, refresh the view.
 | 
			
		||||
        this.pushObserver = CorePushNotificationsDelegate.on<CorePushNotificationsNotificationBasicData>('receive')
 | 
			
		||||
            .subscribe((notification) => {
 | 
			
		||||
 | 
			
		||||
@ -123,10 +123,6 @@
 | 
			
		||||
                <ion-icon *ngIf="conversation.ismuted" name="fas-volume-mute"
 | 
			
		||||
                    [title]="'addon.messages.mutedconversation' | translate"></ion-icon>
 | 
			
		||||
            </h2>
 | 
			
		||||
            <ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount">
 | 
			
		||||
                <ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
 | 
			
		||||
                <span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
 | 
			
		||||
            </ion-note>
 | 
			
		||||
            <p *ngIf="conversation.subname"><core-format-text [text]="conversation.subname" contextLevel="system"
 | 
			
		||||
                [contextInstanceId]="0"></core-format-text></p>
 | 
			
		||||
            <p class="addon-message-last-message">
 | 
			
		||||
@ -139,5 +135,9 @@
 | 
			
		||||
                    class="addon-message-last-message-text" contextLevel="system" [contextInstanceId]="0"></core-format-text>
 | 
			
		||||
            </p>
 | 
			
		||||
        </ion-label>
 | 
			
		||||
        <ion-note *ngIf="conversation.lastmessagedate > 0 || conversation.unreadcount" slot="end">
 | 
			
		||||
            <ion-badge *ngIf="conversation.unreadcount > 0">{{ conversation.unreadcount }}</ion-badge>
 | 
			
		||||
            <span *ngIf="conversation.lastmessagedate > 0">{{conversation.lastmessagedate | coreDateDayOrTime}}</span>
 | 
			
		||||
        </ion-note>
 | 
			
		||||
    </ion-item>
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ export class AddonMessagesIndexLinkHandlerService extends CoreContentLinksHandle
 | 
			
		||||
    name = 'AddonMessagesIndexLinkHandler';
 | 
			
		||||
    pattern = /\/message\/index\.php((?=\d+).)*$/;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the list of actions for a link (url).
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -106,7 +106,6 @@ export class AddonMessagesOfflineProvider {
 | 
			
		||||
            ),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const messageResult:
 | 
			
		||||
        AddonMessagesOfflineAnyMessagesFormatted[] =
 | 
			
		||||
            this.parseMessages(messages);
 | 
			
		||||
@ -379,6 +378,5 @@ export type AddonMessagesOfflineConversationMessagesDBRecordFormatted =
 | 
			
		||||
        useridfrom?: number; // User Id who send the message, will be likely us.
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type AddonMessagesOfflineAnyMessagesFormatted =
 | 
			
		||||
    AddonMessagesOfflineConversationMessagesDBRecordFormatted | AddonMessagesOfflineMessagesDBRecordFormatted;
 | 
			
		||||
 | 
			
		||||
@ -1413,7 +1413,6 @@ export class AddonMessagesProvider {
 | 
			
		||||
        return AddonMessagesMainMenuHandlerService.PAGE_NAME + ( enabled ? '/group-conversations' : '');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get messages according to the params.
 | 
			
		||||
     *
 | 
			
		||||
@ -1749,7 +1748,6 @@ export class AddonMessagesProvider {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
        userId = userId || site.getUserId();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKey(this.getCacheKeyForConversationBetweenUsers(userId, otherUserId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -3065,7 +3063,6 @@ export type AddonMessagesConversationMessageFormatted =
 | 
			
		||||
        showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message.
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by core_message_get_user_message_preferences WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -3559,7 +3556,6 @@ export type AddonMessagesGetUserContactsWSResponse = {
 | 
			
		||||
    }[];
 | 
			
		||||
}[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_message_get_contact_requests WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import { Component, Input, ViewChild, ElementRef } from '@angular/core';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
@ -275,7 +275,6 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
 | 
			
		||||
            cmId: this.module!.id,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        this.summary = submissionStatus.gradingsummary;
 | 
			
		||||
        if (!this.summary) {
 | 
			
		||||
            this.needsGradingAvalaible = false;
 | 
			
		||||
 | 
			
		||||
@ -1190,7 +1190,6 @@ type AddonModAssignSubmissionFeedbackFormatted = AddonModAssignSubmissionFeedbac
 | 
			
		||||
    advancedgrade?: boolean; // Calculated in the app. Whether it uses advanced grading.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type AddonModAssignSubmissionGrade = {
 | 
			
		||||
    method: string;
 | 
			
		||||
    grade?: number | string;
 | 
			
		||||
 | 
			
		||||
@ -346,7 +346,6 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Helper class to manage submissions.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,6 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave {
 | 
			
		||||
    protected blindMarking = false; // Whether it uses blind marking.
 | 
			
		||||
    protected forceLeave = false; // To allow leaving the page without checking for changes.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected route: ActivatedRoute,
 | 
			
		||||
    ) { }
 | 
			
		||||
 | 
			
		||||
@ -212,7 +212,6 @@ export class AddonModAssignHelperProvider {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // If all the plugins were empty (or there were no plugins), we consider the submission to be empty.
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
@ -709,7 +708,6 @@ export class AddonModAssignHelperProvider {
 | 
			
		||||
}
 | 
			
		||||
export const AddonModAssignHelper = makeSingleton(AddonModAssignHelperProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Assign submission with some calculated data.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -290,7 +290,6 @@ export class AddonModAssignOfflineProvider {
 | 
			
		||||
        const promises:
 | 
			
		||||
        Promise<AddonModAssignSubmissionsDBRecordFormatted[] | AddonModAssignSubmissionsGradingDBRecordFormatted[]>[] = [];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        promises.push(this.getAssignSubmissions(assignId, siteId));
 | 
			
		||||
        promises.push(this.getAssignSubmissionsGrade(assignId, siteId));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -178,7 +178,6 @@ export class AddonModAssignSyncProvider extends CoreCourseActivitySyncBaseProvid
 | 
			
		||||
            throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        this.logger.debug('Try to sync assign ' + assignId + ' in site ' + siteId);
 | 
			
		||||
 | 
			
		||||
        const syncPromise = this.performSyncAssign(assignId, siteId);
 | 
			
		||||
@ -553,7 +552,6 @@ export type AddonModAssignSyncResult = {
 | 
			
		||||
    gradesBlocked: number[]; // Whether some grade couldn't be synced because it was blocked. UserId fields of the blocked grade.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to AUTO_SYNCED event.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -761,7 +761,6 @@ export class AddonModAssignProvider {
 | 
			
		||||
        promises.push(this.invalidateAssignmentData(courseId, siteId));
 | 
			
		||||
        promises.push(CoreGrades.invalidateAllCourseGradesData(courseId));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1562,7 +1561,6 @@ export type AddonModAssignSubmissionFeedback = {
 | 
			
		||||
    plugins?: AddonModAssignPlugin[]; // Plugins info.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_assign_list_participants WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -1692,7 +1690,6 @@ type AddonModAssignGetSubmissionStatusWSParams = {
 | 
			
		||||
    groupid?: number; // Filter by users in group (used for generating the grading summary). Empty or 0 for all groups information.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Result of WS mod_assign_get_submission_status.
 | 
			
		||||
 */
 | 
			
		||||
@ -1812,7 +1809,6 @@ type AddonModAssignSubmitGradingFormWSParams = {
 | 
			
		||||
    jsonformdata: string; // The data from the grading form, encoded as a json array.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_assign_save_grade WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -49,7 +49,6 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler {
 | 
			
		||||
     */
 | 
			
		||||
    getComponent?(plugin: AddonModAssignPlugin): Type<unknown> | undefined | Promise<Type<unknown> | undefined>;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return the draft saved data of the feedback plugin.
 | 
			
		||||
     *
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,6 @@ export class AddonModAssignSubmissionCommentsHandlerService implements AddonModA
 | 
			
		||||
    name = 'AddonModAssignSubmissionCommentsHandler';
 | 
			
		||||
    type = 'comments';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the plugin can be edited in offline for existing submissions. In general, this should return false if the
 | 
			
		||||
     * plugin uses Moodle filters. The reason is that the app only prefetches filtered data, and the user should edit
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,6 @@ import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handler';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component to render an onlinetext submission plugin.
 | 
			
		||||
 */
 | 
			
		||||
@ -74,7 +73,6 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
 | 
			
		||||
                this.text = AddonModAssign.getSubmissionPluginText(this.plugin);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            // Set the text.
 | 
			
		||||
            if (!this.edit) {
 | 
			
		||||
                // Not editing, see full text when clicked.
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,6 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
 | 
			
		||||
            // @todo leaveAnimation: 'core-modal-lateral-transition',
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        await modal.present();
 | 
			
		||||
 | 
			
		||||
        const result = await modal.onDidDismiss();
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,6 @@ export const enum AddonModBookNavStyle {
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmaModBook:';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides some features for books.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -160,7 +160,6 @@ export class AddonModFolderProvider {
 | 
			
		||||
}
 | 
			
		||||
export const AddonModFolder = makeSingleton(AddonModFolderProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Folder returned by mod_folder_get_folders_by_courses.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
            [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog"
 | 
			
		||||
            [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog()">
 | 
			
		||||
            [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'far-newspaper'" (action)="gotoBlog()">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline"
 | 
			
		||||
            [priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" [iconAction]="refreshIcon" [closeOnClick]="false"
 | 
			
		||||
 | 
			
		||||
@ -55,9 +55,8 @@
 | 
			
		||||
            flex-grow: 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .addon-mod-forum-discussion-more-info {
 | 
			
		||||
            font-size: 1.4rem;
 | 
			
		||||
            clear: both;
 | 
			
		||||
        .addon-mod-forum-discussion-more-info.ios {
 | 
			
		||||
            font-size: 0.9rem;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1928,7 +1928,6 @@ export type AddonModForumDeletePostWSParams = {
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModForumDeletePostWSResponse = CoreStatusWithWarningsWSResponse;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_forum_get_discussion_post WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -1944,7 +1943,6 @@ export type AddonModForumGetDiscussionPostWSResponse = {
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_forum_get_discussion_posts WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,6 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
 | 
			
		||||
    previousItem = '';
 | 
			
		||||
    nextItem = '';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
 | 
			
		||||
        super('AddonModImscpIndexComponent', courseContentsPage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -343,7 +343,6 @@ export class AddonModImscpProvider {
 | 
			
		||||
}
 | 
			
		||||
export const AddonModImscp = makeSingleton(AddonModImscpProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_imscp_view_imscp WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -387,7 +386,6 @@ type AddonModImscpGetImscpsByCoursesWSResponse = {
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type AddonModImscpTocItem = {
 | 
			
		||||
    href: string;
 | 
			
		||||
    title: string;
 | 
			
		||||
 | 
			
		||||
@ -165,7 +165,6 @@ export class AddonModLabelProvider {
 | 
			
		||||
}
 | 
			
		||||
export const AddonModLabel = makeSingleton(AddonModLabelProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Label returned by mod_label_get_labels_by_courses.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,6 @@ import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { ModalController } from '@singletons';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Modal that asks the password for a lesson.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -175,7 +175,6 @@ export class AddonModPageProvider {
 | 
			
		||||
 | 
			
		||||
export const AddonModPage = makeSingleton(AddonModPageProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page returned by mod_page_get_pages_by_courses.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -251,7 +251,6 @@ export class AddonModResourceModuleHandlerService implements CoreCourseModuleHan
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourceModuleHandler = makeSingleton(AddonModResourceModuleHandlerService);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type AddonResourceHandlerData = {
 | 
			
		||||
    icon: string;
 | 
			
		||||
    extra: string;
 | 
			
		||||
 | 
			
		||||
@ -258,7 +258,6 @@ type AddonModUrlViewUrlWSParams = {
 | 
			
		||||
    urlid: number; // Url instance id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * URL returnd by mod_url_get_urls_by_courses.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -13,9 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { CommonModule } from '@angular/common';
 | 
			
		||||
import { IonicModule } from '@ionic/angular';
 | 
			
		||||
import { TranslateModule } from '@ngx-translate/core';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
 | 
			
		||||
import { AddonNotificationsActionsComponent } from './actions/actions';
 | 
			
		||||
 | 
			
		||||
@ -24,9 +22,7 @@ import { AddonNotificationsActionsComponent } from './actions/actions';
 | 
			
		||||
        AddonNotificationsActionsComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CommonModule,
 | 
			
		||||
        IonicModule,
 | 
			
		||||
        TranslateModule.forChild(),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonNotificationsActionsComponent,
 | 
			
		||||
 | 
			
		||||
@ -176,7 +176,6 @@ export class AddonQbehaviourDeferredFeedbackHandlerService implements CoreQuesti
 | 
			
		||||
 | 
			
		||||
export const AddonQbehaviourDeferredFeedbackHandler = makeSingleton(AddonQbehaviourDeferredFeedbackHandlerService);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Check if a response is complete.
 | 
			
		||||
 *
 | 
			
		||||
 | 
			
		||||
@ -871,7 +871,6 @@ export class AddonQtypeDdMarkerQuestion {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Encapsulates operations on dd area.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,6 @@ import { APP_INITIALIZER, NgModule } from '@angular/core';
 | 
			
		||||
import { CoreQuestionDelegate } from '@features/question/services/question-delegate';
 | 
			
		||||
import { AddonQtypeNumericalHandler } from './services/handlers/numerical';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
@ -128,7 +128,6 @@ export class CoreAttachmentsComponent implements OnInit {
 | 
			
		||||
     */
 | 
			
		||||
    async delete(index: number, askConfirm?: boolean): Promise<void> {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (askConfirm) {
 | 
			
		||||
            try {
 | 
			
		||||
                await CoreDomUtils.showDeleteConfirm('core.confirmdeletefile');
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,6 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
 | 
			
		||||
    protected instanceId: string;
 | 
			
		||||
    protected parentContextMenu?: CoreContextMenuComponent;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    constructor(
 | 
			
		||||
        elementRef: ElementRef,
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
@ -77,6 +77,10 @@ export class CoreInfiniteLoadingComponent implements OnChanges {
 | 
			
		||||
 | 
			
		||||
        // Calculate distance from edge.
 | 
			
		||||
        const content = this.element.nativeElement.closest('ion-content') as IonContent;
 | 
			
		||||
        if (!content) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const scrollElement = await content.getScrollElement();
 | 
			
		||||
 | 
			
		||||
        const infiniteHeight = this.element.nativeElement.getBoundingClientRect().height;
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,7 @@
 | 
			
		||||
    --menu-border-width: 0;
 | 
			
		||||
    --menu-box-shadow: none;
 | 
			
		||||
    --menu-z: 0;
 | 
			
		||||
    --selected-item-border-width: 0;
 | 
			
		||||
    --selected-item-color: transparent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:host(.content-only) {
 | 
			
		||||
 | 
			
		||||
@ -518,7 +518,6 @@ export type CoreCommentsDataWithUser = CoreCommentsData & {
 | 
			
		||||
    deleted?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type CoreCommentsOfflineWithUser = CoreCommentsDBRecord & {
 | 
			
		||||
    profileimageurl?: string;
 | 
			
		||||
    fullname?: string;
 | 
			
		||||
 | 
			
		||||
@ -322,7 +322,6 @@ export type CoreCommentsSyncResult = {
 | 
			
		||||
    updated: boolean; // Whether some data was sent to the server or offline data was updated.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data passed to AUTO_SYNCED event.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -530,7 +530,6 @@ export type CoreCommentsArea = {
 | 
			
		||||
    canpostorhascomments: boolean; // Canpostorhascomments.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_comment_add_comments WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,6 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,6 @@ export class CoreCourseModuleMainActivityPage<ActivityType extends CoreCourseMod
 | 
			
		||||
    module!: CoreCourseAnyModuleData;
 | 
			
		||||
    courseId!: number;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
@ -13,9 +13,13 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { AddonBlog } from '@addons/blog/services/blog';
 | 
			
		||||
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
 | 
			
		||||
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { IonRefresher } from '@ionic/angular';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
 | 
			
		||||
@ -90,7 +94,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
        this.componentId = this.module?.id;
 | 
			
		||||
        this.externalUrl = this.module?.url;
 | 
			
		||||
        this.courseId = this.courseId || this.module?.course;
 | 
			
		||||
        // @todo this.blog = await this.blogProvider.isPluginEnabled();
 | 
			
		||||
        this.blog = await AddonBlog.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -223,8 +227,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
 | 
			
		||||
     * Go to blog posts.
 | 
			
		||||
     */
 | 
			
		||||
    async gotoBlog(): Promise<void> {
 | 
			
		||||
        // const params: Params = { cmId: this.module?.id };
 | 
			
		||||
        // @todo return CoreNavigator.navigateToSitePath('AddonBlogEntriesPage', { params });
 | 
			
		||||
        const params: Params = { cmId: this.module?.id };
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -208,7 +208,6 @@ export class CoreCourseProvider {
 | 
			
		||||
            preSets.emergencyCache = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const data = await site.read<CoreCourseCompletionActivityStatusWSResponse>(
 | 
			
		||||
            'core_completion_get_activities_completion_status',
 | 
			
		||||
            params,
 | 
			
		||||
@ -551,7 +550,6 @@ export class CoreCourseProvider {
 | 
			
		||||
            return grade;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -508,7 +508,6 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate<CoreCo
 | 
			
		||||
            return downloadedSize;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        const cachedSize = await site.getComponentCacheSize(handler.component, module.id);
 | 
			
		||||
 | 
			
		||||
        return cachedSize + downloadedSize;
 | 
			
		||||
 | 
			
		||||
@ -146,7 +146,6 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider<CoreCourseSyncR
 | 
			
		||||
            <CoreCourseManualCompletionDBRecord[]> [],
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (!completions || !completions.length) {
 | 
			
		||||
            // Nothing to sync, set sync time.
 | 
			
		||||
            await this.setSyncTime(courseId, siteId);
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,6 @@ import { CoreCoursesComponentsModule } from '../../components/components.module'
 | 
			
		||||
 | 
			
		||||
import { CoreCoursesAvailableCoursesPage } from './available-courses';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,6 @@ import { CoreCoursesComponentsModule } from '../../components/components.module'
 | 
			
		||||
 | 
			
		||||
import { CoreCoursesCategoriesPage } from './categories';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: '',
 | 
			
		||||
 | 
			
		||||
@ -138,7 +138,6 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle download enabled.
 | 
			
		||||
     */
 | 
			
		||||
@ -179,5 +178,4 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy {
 | 
			
		||||
        this.updateSiteObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,6 @@ export class CoreCoursesDashboardProvider {
 | 
			
		||||
 | 
			
		||||
export const CoreCoursesDashboard = makeSingleton(CoreCoursesDashboardProvider);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_block_get_dashboard_blocks WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@ import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { CoreCourses } from '../courses';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler for course request push notifications clicks.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -370,7 +370,6 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
 | 
			
		||||
            const height = this.streamVideo?.nativeElement.videoHeight;
 | 
			
		||||
            const loadingModal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            this.imgCanvas.nativeElement.width = width;
 | 
			
		||||
            this.imgCanvas.nativeElement.height = height;
 | 
			
		||||
            this.imgCanvas.nativeElement.getContext('2d').drawImage(this.streamVideo?.nativeElement, 0, 0, width, height);
 | 
			
		||||
 | 
			
		||||
@ -51,7 +51,6 @@ export class CoreEmulatorHelperProvider {
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return CoreUtils.allPromises(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            flex-direction: column;
 | 
			
		||||
            ion-tab-button {
 | 
			
		||||
                display: contents;
 | 
			
		||||
                width: 100%;
 | 
			
		||||
                ion-badge {
 | 
			
		||||
                    top: calc(50% - 20px);
 | 
			
		||||
 | 
			
		||||
@ -109,7 +109,6 @@ export const SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data stored in DB for badge.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -128,7 +128,6 @@ export class CoreQuestionComponent implements OnInit {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Load local answers if offline is enabled.
 | 
			
		||||
        if (this.offlineEnabled && this.component && this.attemptId) {
 | 
			
		||||
            await CoreQuestionHelper.loadLocalAnswers(this.question, this.component, this.attemptId);
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,6 @@ import {
 | 
			
		||||
    QUESTION_TABLE_NAME,
 | 
			
		||||
} from './database/question';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const QUESTION_PREFIX_REGEX = /q\d+:(\d+)_/;
 | 
			
		||||
const STATES: Record<string, CoreQuestionState> = {
 | 
			
		||||
    todo: {
 | 
			
		||||
 | 
			
		||||
@ -76,7 +76,6 @@ export const RATINGS_SITE_SCHEMA: CoreSiteSchema = {
 | 
			
		||||
    ],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Primary data to identify a stored rating.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,6 @@ import { CoreEvents } from '@singletons/events';
 | 
			
		||||
import { CoreRating } from './rating';
 | 
			
		||||
import { CoreRatingItemSet, CoreRatingOffline } from './rating-offline';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service to sync ratings.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -513,7 +513,6 @@ export type CoreRatingItemRating = {
 | 
			
		||||
    timemodified: number; // Time modified (timestamp).
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_rating_get_item_ratings WS.
 | 
			
		||||
 */
 | 
			
		||||
@ -535,7 +534,6 @@ export type CoreRatingGetItemRatingsWSResponse = {
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of core_rating_add_rating WS.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,6 @@ import { NgModule } from '@angular/core';
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreSearchBoxComponent } from './search-box/search-box';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        CoreSearchBoxComponent,
 | 
			
		||||
 | 
			
		||||
@ -159,7 +159,6 @@ export class CoreSearchBoxComponent implements OnInit {
 | 
			
		||||
        if (!this.formElement) {
 | 
			
		||||
            this.formElement = event.detail.target.closest('form');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            this.formElement?.addEventListener('blur', () => {
 | 
			
		||||
                // Wait the new element to be focused.
 | 
			
		||||
                setTimeout(() => {
 | 
			
		||||
 | 
			
		||||
@ -94,7 +94,6 @@ export class CoreSettingsGeneralPage {
 | 
			
		||||
                selected: value === this.selectedZoomLevel,
 | 
			
		||||
            }));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        this.richTextEditor = await CoreConfig.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true);
 | 
			
		||||
 | 
			
		||||
        this.debugDisplay = await CoreConfig.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false);
 | 
			
		||||
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user