redesign 2023
This commit is contained in:
35
themes/FixIt/assets/js/custom.js.example
Normal file
35
themes/FixIt/assets/js/custom.js.example
Normal file
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Custom javascript for FixIt site.
|
||||
* @author @Lruihao https://lruihao.cn
|
||||
*/
|
||||
const FixItCustom = new (function () {
|
||||
/**
|
||||
* Hello World
|
||||
* You can define your own functions below.
|
||||
* @returns {FixItCustom}
|
||||
*/
|
||||
this.hello = () => {
|
||||
console.log('FixItCustom echo: Hello FixIt!');
|
||||
return this;
|
||||
};
|
||||
/**
|
||||
* Initialize.
|
||||
* @returns {FixItCustom}
|
||||
*/
|
||||
this.init = () => {
|
||||
// Custom infos.
|
||||
this.hello();
|
||||
return this;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Immediate.
|
||||
*/
|
||||
(() => {
|
||||
FixItCustom.init();
|
||||
// It will be executed when the DOM tree is built.
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// FixItCustom.init();
|
||||
});
|
||||
})();
|
223
themes/FixIt/assets/js/fixit-decryptor.js
Normal file
223
themes/FixIt/assets/js/fixit-decryptor.js
Normal file
@ -0,0 +1,223 @@
|
||||
/**
|
||||
* FixIt decryptor for encrypted pages and fixit-encryptor shortcode
|
||||
* @param {Object} options
|
||||
* @param {Function} [options.decrypted] [Lifecycle Hooks] handler after decrypting
|
||||
* @param {Function} [options.reset] [Lifecycle Hooks] handler after encrypting again
|
||||
* @param {Number} [options.duration=86400] number of seconds to cache decryption statistics. unit: s
|
||||
* @author @Lruihao https://lruihao.cn
|
||||
* @since v0.2.15
|
||||
*/
|
||||
FixItDecryptor = function (options = {}) {
|
||||
var _proto = FixItDecryptor.prototype;
|
||||
this.options = options || {};
|
||||
this.options.duration = this.options.duration || 24 * 60 * 60; // default cache one day
|
||||
this.decryptedEventSet = new Set();
|
||||
this.resetEventSet = new Set();
|
||||
this.$el = document.querySelector('.fixit-decryptor-container');
|
||||
|
||||
/**
|
||||
* decrypt content
|
||||
* @param {String} base64EncodeContent encrypted content
|
||||
*/
|
||||
var _decryptContent = (base64EncodeContent) => {
|
||||
try {
|
||||
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
|
||||
this.$el.querySelector('#fixit-decryptor-input').classList.add('d-none');
|
||||
this.$el.querySelector('.fixit-encryptor-btn').classList.remove('d-none');
|
||||
document.querySelector('#content').insertAdjacentHTML(
|
||||
'afterbegin',
|
||||
CryptoJS.enc.Base64.parse(base64EncodeContent).toString(CryptoJS.enc.Utf8)
|
||||
);
|
||||
} catch (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
// decrypted hook
|
||||
console.log(this.decryptedEventSet)
|
||||
for (const event of this.decryptedEventSet) {
|
||||
event();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* initialize FixIt decryptor
|
||||
*/
|
||||
_proto.init = () => {
|
||||
this.addEventListener('decrypted', this.options?.decrypted);
|
||||
this.addEventListener('reset', this.options?.reset);
|
||||
this.validateCache();
|
||||
|
||||
const _decryptor = this;
|
||||
this.$el.querySelector('#fixit-decryptor-input')?.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const $content = document.querySelector('#content');
|
||||
const password = $content.getAttribute('data-password');
|
||||
const input = this.value.trim();
|
||||
const saltLen = input.length % 2 ? input.length : input.length + 1;
|
||||
const inputMd5 = CryptoJS.MD5(input).toString();
|
||||
const inputSha256 = CryptoJS.SHA256(input).toString();
|
||||
|
||||
this.value = '';
|
||||
this.blur();
|
||||
if (!input) {
|
||||
alert('Please enter the correct password!');
|
||||
return console.warn('Please enter the correct password!');
|
||||
}
|
||||
if (inputMd5 !== password) {
|
||||
alert(`Password error: ${input} not the correct password!`);
|
||||
return console.warn(`Password error: ${input} not the correct password!`);
|
||||
}
|
||||
// cache decryption statistics
|
||||
window.localStorage?.setItem(
|
||||
`fixit-decryptor/#${location.pathname}`,
|
||||
JSON.stringify({
|
||||
expiration: Math.ceil(Date.now() / 1000) + _decryptor.options.duration,
|
||||
md5: inputMd5,
|
||||
sha256: inputSha256.slice(saltLen)
|
||||
})
|
||||
);
|
||||
_decryptContent($content.getAttribute('data-content').replace(inputSha256.slice(saltLen), ''));
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.querySelector('.fixit-encryptor-btn')?.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
this.classList.add('d-none')
|
||||
_decryptor.$el.querySelector('#fixit-decryptor-input').classList.remove('d-none');
|
||||
document.querySelector('#content').innerHTML = '';
|
||||
document.querySelector('#content').insertAdjacentElement(
|
||||
'afterbegin',
|
||||
_decryptor.$el
|
||||
);
|
||||
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
|
||||
// reset hook
|
||||
for (const event of _decryptor.resetEventSet) {
|
||||
event();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* initialize fixit-encryptor shortcodes
|
||||
*/
|
||||
_proto.initShortcodes = () => {
|
||||
// TODO TODO shortcode decrypted event
|
||||
// this.addEventListener('decrypted', this.options?.decrypted);
|
||||
const _decryptor = this;
|
||||
const $shortcodes = document.querySelectorAll('fixit-encryptor:not(.decrypted)');
|
||||
|
||||
$shortcodes.forEach($shortcode => {
|
||||
$shortcode.querySelector('.fixit-decryptor-input')?.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const $decryptor = this.parentElement.parentElement;
|
||||
const $content = $decryptor.nextElementSibling;
|
||||
const password = $content.getAttribute('data-password');
|
||||
const input = this.value.trim();
|
||||
const saltLen = input.length % 2 ? input.length : input.length + 1;
|
||||
const inputMd5 = CryptoJS.MD5(input).toString();
|
||||
const inputSha256 = CryptoJS.SHA256(input).toString();
|
||||
|
||||
this.value = '';
|
||||
this.blur();
|
||||
if (!input) {
|
||||
alert('Please enter the correct password!');
|
||||
return console.warn('Please enter the correct password!');
|
||||
}
|
||||
if (inputMd5 !== password) {
|
||||
alert(`Password error: ${input} not the correct password!`);
|
||||
return console.warn(`Password error: ${input} not the correct password!`);
|
||||
}
|
||||
try {
|
||||
const base64EncodeContent = $content.getAttribute('data-content').replace(inputSha256.slice(saltLen), '');
|
||||
$decryptor.querySelector('.fixit-decryptor-input').classList.add('d-none');
|
||||
$content.insertAdjacentHTML(
|
||||
'afterbegin',
|
||||
CryptoJS.enc.Base64.parse(base64EncodeContent).toString(CryptoJS.enc.Utf8)
|
||||
);
|
||||
$decryptor.parentElement.classList.add('decrypted');
|
||||
} catch (err) {
|
||||
return console.error(err);
|
||||
}
|
||||
// TODO shortcode decrypted hook
|
||||
// for (const event of _decryptor.decryptedEventSet) {
|
||||
// event();
|
||||
// }
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* validate the cached decryption statistics in localStorage
|
||||
* @returns {FixItDecryptor}
|
||||
*/
|
||||
_proto.validateCache = () => {
|
||||
const $content = document.querySelector('#content');
|
||||
const password = $content.getAttribute('data-password');
|
||||
const cachedStat = JSON.parse(window.localStorage?.getItem(`fixit-decryptor/#${location.pathname}`));
|
||||
|
||||
if (!cachedStat) {
|
||||
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
|
||||
this.$el.querySelector('#fixit-decryptor-input').classList.remove('d-none');
|
||||
return this;
|
||||
}
|
||||
if (cachedStat?.md5 !== password || Number(cachedStat?.expiration) < Math.ceil(Date.now() / 1000)) {
|
||||
this.$el.querySelector('.fixit-decryptor-loading').classList.add('d-none');
|
||||
this.$el.querySelector('#fixit-decryptor-input').classList.remove('d-none');
|
||||
window.localStorage?.removeItem(`fixit-decryptor/#${location.pathname}`);
|
||||
console.warn('The password has expired, please re-enter!');
|
||||
return this;
|
||||
}
|
||||
_decryptContent($content.getAttribute('data-content').replace(cachedStat.sha256, ''));
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* add event listener for FixIt Decryptor
|
||||
* @param {String} event event name
|
||||
* @param {Function} listener event handler
|
||||
* @returns {FixItDecryptor}
|
||||
*/
|
||||
_proto.addEventListener = (event, listener) => {
|
||||
if (typeof listener !== 'function') {
|
||||
return this;
|
||||
}
|
||||
switch (event) {
|
||||
case 'decrypted':
|
||||
this.decryptedEventSet.add(listener);
|
||||
break;
|
||||
case 'reset':
|
||||
this.resetEventSet.add(listener);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Event ${event} not found in FixIt Decryptor!`);
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove event listener for FixIt Decryptor
|
||||
* @param {String} event event name
|
||||
* @param {Function} listener event handler
|
||||
* @returns {FixItDecryptor}
|
||||
*/
|
||||
_proto.removeEventListener = (event, listener) => {
|
||||
if (typeof listener !== 'function') {
|
||||
return this;
|
||||
}
|
||||
switch (event) {
|
||||
case 'decrypted':
|
||||
this.decryptedEventSet.delete(listener);
|
||||
break;
|
||||
case 'reset':
|
||||
this.resetEventSet.delete(listener);
|
||||
break;
|
||||
default:
|
||||
console.warn(`Event ${event} not found in FixIt Decryptor!`);
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
};
|
||||
};
|
269
themes/FixIt/assets/js/service-worker.js
Normal file
269
themes/FixIt/assets/js/service-worker.js
Normal file
@ -0,0 +1,269 @@
|
||||
/**
|
||||
* Service Worker
|
||||
* imported from https://github.com/HEIGE-PCloud/DoIt/blob/v0.2.11/src/js/sw.js
|
||||
*/
|
||||
CACHE_VERSION = 1;
|
||||
|
||||
const BASE_CACHE_FILES = [
|
||||
'/css/style.min.css',
|
||||
'/js/theme.min.js',
|
||||
'/site.webmanifest',
|
||||
'/fixit.min.svg'
|
||||
];
|
||||
|
||||
const OFFLINE_CACHE_FILES = [
|
||||
'/css/style.min.css',
|
||||
'/js/theme.min.js',
|
||||
'/site.webmanifest',
|
||||
'/fixit.min.svg',
|
||||
'/offline/'
|
||||
];
|
||||
|
||||
const NOT_FOUND_CACHE_FILES = [
|
||||
'/css/style.min.css',
|
||||
'/js/theme.min.js',
|
||||
'/site.webmanifest',
|
||||
'/fixit.min.svg',
|
||||
'/404.html'
|
||||
];
|
||||
|
||||
const OFFLINE_PAGE = '/offline/';
|
||||
const NOT_FOUND_PAGE = '/404.html';
|
||||
|
||||
const CACHE_VERSIONS = {
|
||||
assets: 'assets-v' + CACHE_VERSION,
|
||||
content: 'content-v' + CACHE_VERSION,
|
||||
offline: 'offline-v' + CACHE_VERSION,
|
||||
notFound: '404-v' + CACHE_VERSION
|
||||
};
|
||||
|
||||
// Define MAX_TTL's in SECONDS for specific file extensions
|
||||
const MAX_TTL = {
|
||||
'/': 3600,
|
||||
html: 3600,
|
||||
json: 86400,
|
||||
js: 86400,
|
||||
css: 86400
|
||||
};
|
||||
|
||||
const CACHE_BLACKLIST = [
|
||||
(str) => {
|
||||
return !str.startsWith('http://localhost');
|
||||
}
|
||||
];
|
||||
|
||||
const SUPPORTED_METHODS = ['GET'];
|
||||
|
||||
/**
|
||||
* isBlackListed
|
||||
* @param {string} url
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isBlacklisted(url) {
|
||||
return CACHE_BLACKLIST.length > 0
|
||||
? !CACHE_BLACKLIST.filter((rule) => {
|
||||
if (typeof rule === 'function') {
|
||||
return !rule(url);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}).length
|
||||
: false;
|
||||
}
|
||||
|
||||
/**
|
||||
* getFileExtension
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
function getFileExtension(url) {
|
||||
const extension = url.split('.').reverse()[0].split('?')[0];
|
||||
return extension.endsWith('/') ? '/' : extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* getTTL
|
||||
* @param {string} url
|
||||
*/
|
||||
function getTTL(url) {
|
||||
if (typeof url === 'string') {
|
||||
const extension = getFileExtension(url);
|
||||
if (typeof MAX_TTL[extension] === 'number') {
|
||||
return MAX_TTL[extension];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* installServiceWorker
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function installServiceWorker() {
|
||||
return Promise.all([
|
||||
caches.open(CACHE_VERSIONS.assets).then((cache) => {
|
||||
return cache.addAll(BASE_CACHE_FILES);
|
||||
}),
|
||||
caches.open(CACHE_VERSIONS.offline).then((cache) => {
|
||||
return cache.addAll(OFFLINE_CACHE_FILES);
|
||||
}),
|
||||
caches.open(CACHE_VERSIONS.notFound).then((cache) => {
|
||||
return cache.addAll(NOT_FOUND_CACHE_FILES);
|
||||
})
|
||||
]).then(() => {
|
||||
return self.skipWaiting();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* cleanupLegacyCache
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function cleanupLegacyCache() {
|
||||
const currentCaches = Object.keys(CACHE_VERSIONS).map((key) => {
|
||||
return CACHE_VERSIONS[key];
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
caches
|
||||
.keys()
|
||||
.then((keys) => {
|
||||
return keys.filter((key) => {
|
||||
return !~currentCaches.indexOf(key);
|
||||
});
|
||||
})
|
||||
.then((legacy) => {
|
||||
if (legacy.length) {
|
||||
Promise.all(
|
||||
legacy.map((legacyKey) => {
|
||||
return caches.delete(legacyKey);
|
||||
})
|
||||
)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
event.waitUntil(Promise.all([installServiceWorker(), self.skipWaiting()]));
|
||||
});
|
||||
|
||||
// The activate handler takes care of cleaning up old caches.
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
cleanupLegacyCache(),
|
||||
self.clients.claim(),
|
||||
self.skipWaiting()
|
||||
]).catch((err) => {
|
||||
console.log(err);
|
||||
self.skipWaiting();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
event.respondWith(
|
||||
caches.open(CACHE_VERSIONS.content).then((cache) => {
|
||||
return cache
|
||||
.match(event.request)
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
const headers = response.headers.entries();
|
||||
let date = null;
|
||||
|
||||
for (const pair of headers) {
|
||||
if (pair[0] === 'date') {
|
||||
date = new Date(pair[1]);
|
||||
}
|
||||
}
|
||||
if (date) {
|
||||
const age = parseInt(
|
||||
(new Date().getTime() - date.getTime()) / 1000
|
||||
);
|
||||
const ttl = getTTL(event.request.url);
|
||||
|
||||
if (ttl && age > ttl) {
|
||||
return new Promise((resolve) => {
|
||||
return fetch(event.request.clone())
|
||||
.then((updatedResponse) => {
|
||||
if (updatedResponse) {
|
||||
cache.put(event.request, updatedResponse.clone());
|
||||
resolve(updatedResponse);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
resolve(response);
|
||||
});
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
return response;
|
||||
});
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
return response;
|
||||
} else {
|
||||
return fetch(event.request.clone())
|
||||
.then((response) => {
|
||||
if (response.status < 400) {
|
||||
if (
|
||||
~SUPPORTED_METHODS.indexOf(event.request.method) &&
|
||||
!isBlacklisted(event.request.url) &&
|
||||
event.request.url.slice(0, 4) === 'http'
|
||||
) {
|
||||
cache.put(event.request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} else {
|
||||
return caches.open(CACHE_VERSIONS.notFound).then((cache) => {
|
||||
return cache.match(NOT_FOUND_PAGE);
|
||||
});
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
return caches
|
||||
.open(CACHE_VERSIONS.offline)
|
||||
.then((offlineCache) => {
|
||||
return offlineCache.match(OFFLINE_PAGE);
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(' Error in fetch handler:', error);
|
||||
throw error;
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
3
themes/FixIt/assets/js/theme.min.js
vendored
Normal file
3
themes/FixIt/assets/js/theme.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
themes/FixIt/assets/js/theme.min.js.map
Normal file
1
themes/FixIt/assets/js/theme.min.js.map
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user