mirror of
https://github.com/torappinfo/uweb.git
synced 2024-08-14 23:54:59 +00:00
simplify mdict parser
This commit is contained in:
parent
e707f7a0d4
commit
f82c34ba62
13 changed files with 1918 additions and 44 deletions
|
@ -6,18 +6,21 @@
|
||||||
<a href="https://repo.or.cz/uweb.git/blob_plain/HEAD:/zh/index.html">repo</a>
|
<a href="https://repo.or.cz/uweb.git/blob_plain/HEAD:/zh/index.html">repo</a>
|
||||||
<a href="https://jamesfengcao.codeberg.page/zh/">codeberg</a>
|
<a href="https://jamesfengcao.codeberg.page/zh/">codeberg</a>
|
||||||
<a href="https://uwebzh.netlify.app/zh/">netlify</a>
|
<a href="https://uwebzh.netlify.app/zh/">netlify</a>
|
||||||
|
<a href="https://ik4ev-laaaa-aaaad-qd4sq-cai.ic.fleek.co/zh/">fleek</a>
|
||||||
<a href="https://uweb.stormkit.dev/zh/">stormkit</a>
|
<a href="https://uweb.stormkit.dev/zh/">stormkit</a>
|
||||||
<a href="https://uweb.surge.sh/zh/">surge</a>
|
<a href="https://uweb.surge.sh/zh/">surge</a>
|
||||||
<a href="https://torappinfo.github.io/uweb/zh/">github</a>
|
<a href="https://torappinfo.github.io/uweb/zh/">github</a>
|
||||||
<a href="i:0m04:https://code.aliyun.com/torappinfo/uweb/raw/master/redirect.html">试验</a>
|
<a href="i:0mi:5h:https://gitee.com/jamesfengcao/uweb/raw/master/redirect.html">试验</a>
|
||||||
<a href="i:0m04:https://gitcode.net/spoony1971/uweb/-/raw/master/redirect.html">试验2</a>
|
<a href="i:0m04:https://gitcode.net/spoony1971/uweb/-/raw/master/redirect.html">试验1</a>
|
||||||
<a href="i:0mi:5h:https://jihulab.com/torappinfo/uweb/-/raw/master/redirect.html">试验3</a>
|
<a href="i:0mi:5h:https://jihulab.com/torappinfo/uweb/-/raw/master/redirect.html">试验2</a>
|
||||||
<a href="i:0m04:https://gitea.com/torappinfo/uweb/raw/branch/master/redirect.html">试验4</a>
|
<a href="i:0m04:https://www.gitclone.com/gogs/jamesfengcao/uweb/raw/master/redirect.html">试验3</a>
|
||||||
|
<a href="i:0m04:https://code.aliyun.com/torappinfo/uweb/raw/master/redirect.html">试验4</a>
|
||||||
|
<a href="i:0m04:https://gitea.com/torappinfo/uweb/raw/branch/master/redirect.html">试验5</a>
|
||||||
|
|
||||||
|
<a href="i:0mi:5h?format=raw::https://sourceforge.net/p/uwebbrowser/code/ci/master/tree/redirect.html">试验6</a>
|
||||||
|
<a href="i:0mi:5h?format=raw::https://rocketgit.com/user/torappinfo/uweb/source/tree/branch/master/blob_download/redirect.html">试验7</a>
|
||||||
|
<a href="i:0m04:https://notabug.org/torappinfo/uweb/raw/master/redirect.html">试验8</a>
|
||||||
|
|
||||||
<a href="i:0mi:5h?format=raw::https://sourceforge.net/p/uwebbrowser/code/ci/master/tree/redirect.html">试验5</a>
|
|
||||||
<a href="i:0mi:5h?format=raw::https://rocketgit.com/user/torappinfo/uweb/source/tree/branch/master/blob_download/redirect.html">试验6</a>
|
|
||||||
<a href="i:0m04:https://notabug.org/torappinfo/uweb/raw/master/redirect.html">试验7</a>
|
|
||||||
<a href="i:0mi:5h:https://gitee.com/jamesfengcao/uweb/raw/master/redirect.html">试验8</a>
|
|
||||||
<a href="i:0mi:5h:https://git.launchpad.net/uweb/plain/redirect.html">试验9</a>
|
<a href="i:0mi:5h:https://git.launchpad.net/uweb/plain/redirect.html">试验9</a>
|
||||||
<a href="i:0m04:https://framagit.org/torappinfo/uweb/-/raw/master/redirect.html">试验10</a>
|
<a href="i:0m04:https://framagit.org/torappinfo/uweb/-/raw/master/redirect.html">试验10</a>
|
||||||
<a href="i:0m04:https://agit.ai/jamesfengcao/uweb/raw/branch/master/redirect.html">试验11</a>
|
<a href="i:0m04:https://agit.ai/jamesfengcao/uweb/raw/branch/master/redirect.html">试验11</a>
|
||||||
|
@ -28,6 +31,7 @@
|
||||||
<a href="i:0mi:5h:https://pagure.io/uweb/raw/master/f/redirect.html">试验16</a>
|
<a href="i:0mi:5h:https://pagure.io/uweb/raw/master/f/redirect.html">试验16</a>
|
||||||
|
|
||||||
<a href="i:0mi:5h:https://fastly.jsdelivr.net/gh/torappinfo/uweb/redirect.html">试验17</a>
|
<a href="i:0mi:5h:https://fastly.jsdelivr.net/gh/torappinfo/uweb/redirect.html">试验17</a>
|
||||||
|
|
||||||
<a href="https://torappinfo.bitbucket.io/zh/">bitbucket</a>
|
<a href="https://torappinfo.bitbucket.io/zh/">bitbucket</a>
|
||||||
<a href="https://uweb-zh.vercel.app/zh/">vercel</a>
|
<a href="https://uweb-zh.vercel.app/zh/">vercel</a>
|
||||||
<a href="https://uwebzh.pages.dev/zh/">pages</a>
|
<a href="https://uwebzh.pages.dev/zh/">pages</a>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="generator" content="Hugo 0.104.2" />
|
<meta name="generator" content="Hugo 0.104.3" />
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,10 @@ Wait to receive url from the network and open the url (not showing ip)</p>
|
||||||
Launch app for specific <action>.</p>
|
Launch app for specific <action>.</p>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
<p>i:1e<br>
|
||||||
|
Open file manager for removable SDCARD download folder</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<p>i:50[path]:[string]<br>
|
<p>i:50[path]:[string]<br>
|
||||||
Save [string] as file</p>
|
Save [string] as file</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
22
en/searchurl/mdict/mdict-common.js
Normal file
22
en/searchurl/mdict/mdict-common.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
var MCommon = (function () {
|
||||||
|
return {
|
||||||
|
/**
|
||||||
|
* Get file extension.
|
||||||
|
*/
|
||||||
|
getExtension: function (filename, defaultExt) {
|
||||||
|
return /(?:\.([^.]+))?$/.exec(filename)[1] || defaultExt;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression to strip key if dictionary's "StripKey" attribute is true.
|
||||||
|
*/
|
||||||
|
REGEXP_STRIPKEY: {
|
||||||
|
'mdx' : /[()., '/\\@_-]()/g,
|
||||||
|
'mdd' : /([.][^.]*$)|[()., '/\\@_-]/g // strip '.' before file extension that is keeping the last period
|
||||||
|
},
|
||||||
|
|
||||||
|
log: function() {
|
||||||
|
console.log.apply(console, [].slice.apply(arguments));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}());
|
938
en/searchurl/mdict/mdict-parser.js
Normal file
938
en/searchurl/mdict/mdict-parser.js
Normal file
|
@ -0,0 +1,938 @@
|
||||||
|
//define(['pako', 'lzo', 'ripemd128', 'bluebird', 'mdict-parseXml', 'mdict-MCommon']
|
||||||
|
//pako, lzo, ripemd128, Promise, parseXml, MCommon
|
||||||
|
var parseXml = function (str) {
|
||||||
|
return (new DOMParser()).parseFromString(str, 'text/xml');
|
||||||
|
};
|
||||||
|
var MParser = (function (){
|
||||||
|
// Value of undefined.
|
||||||
|
var UNDEFINED = void 0;
|
||||||
|
|
||||||
|
// A shared UTF-16LE text decorder used to read dictionary header string.
|
||||||
|
var UTF_16LE = new TextDecoder('utf-16le');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the first argument as result.
|
||||||
|
* This function is used to simulate consequence, i.e. read data and return it, then forward to a new position.
|
||||||
|
* @param any data or function call
|
||||||
|
* @return the first arugment
|
||||||
|
*/
|
||||||
|
function conseq(/* args... */) { return arguments[0]; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decrypt encrypted data block of keyword index (attrs.Encrypted = "2").
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#keyword-index-encryption
|
||||||
|
* @param buf an ArrayBuffer containing source data
|
||||||
|
* @param key an ArrayBuffer holding decryption key, which will be supplied to ripemd128() before decryption
|
||||||
|
* @return an ArrayBuffer carrying decrypted data, occupying the same memory space of source buffer
|
||||||
|
*/
|
||||||
|
function decrypt(buf, key) {
|
||||||
|
key = ripemd128(key);
|
||||||
|
var byte, keylen = key.length, prev = 0x36, i = 0, len = buf.length;
|
||||||
|
for (; i < len; i++) {
|
||||||
|
byte = buf[i];
|
||||||
|
byte = ((byte >> 4) | (byte << 4) ); // & 0xFF; <-- it's already a byte
|
||||||
|
byte = byte ^ prev ^ (i & 0xFF) ^ key[i % keylen];
|
||||||
|
prev = buf[i];
|
||||||
|
buf[i] = byte;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For sliceThen(..).exec(proc, ..), mark what proc function returns is multiple values
|
||||||
|
* to be passed to further Promise#spread(..) call.
|
||||||
|
*/
|
||||||
|
function spreadus() {
|
||||||
|
var args = Array.prototype.slice.apply(arguments);
|
||||||
|
args._spreadus_ = true;
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slice part of a file/blob object, return a promise object which will resolve to an ArrayBuffer to feed subsequent process.
|
||||||
|
* The returned promise object is extened with an exec(proc, args...) method which can be chained with further process.
|
||||||
|
* @param file file or blob object
|
||||||
|
* @param offset start position to slice
|
||||||
|
* @param len length to slice
|
||||||
|
* @return a promise object which will resolve to an ArrayBuffer containing data been read
|
||||||
|
*/
|
||||||
|
function sliceThen(file, offset, len) {
|
||||||
|
var p = new Promise(function(_resolve) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function() { _resolve(reader.result); }
|
||||||
|
reader.readAsArrayBuffer(file.slice(offset, offset + len));
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call proc with specified arguments prepending with sliced file/blob data (ArrayBuffer) been read.
|
||||||
|
* @param the first argument is a function to be executed
|
||||||
|
* @param other optional arguments are passed to the function following auto supplied input ArrayBuffer
|
||||||
|
* @return a promise object which can be chained with further process through spread() method
|
||||||
|
*/
|
||||||
|
p.exec = function(proc /*, args... */) {
|
||||||
|
var args = Array.prototype.slice.call(arguments, 1);
|
||||||
|
return p.then(function(data) {
|
||||||
|
args.unshift(data);
|
||||||
|
var ret = proc.apply(null, args);
|
||||||
|
return resolve(ret !== UNDEFINED && ret._spreadus_ ? ret : [ret]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap value as a resolved promise.
|
||||||
|
*/
|
||||||
|
function resolve(value) { return Promise.resolve(value); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap value as a rejected promise.
|
||||||
|
*/
|
||||||
|
function reject(reason) { return Promise.reject(reason); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Harvest any resolved promises, if all failed then return reasons.
|
||||||
|
*/
|
||||||
|
function harvest(outcomes) {
|
||||||
|
return Promise.settle(outcomes).then(function(results) {
|
||||||
|
if (results.length === 0) {
|
||||||
|
return reject("** NOT FOUND **");
|
||||||
|
}
|
||||||
|
|
||||||
|
var solved = [], failed = [];
|
||||||
|
for (var i = 0; i < results.length; i++) {
|
||||||
|
if (results[i].isResolved()) {
|
||||||
|
solved.push(results[i].value());
|
||||||
|
} else {
|
||||||
|
failed.push(results[i].reason());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return solved.length ? solved : failed;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a Record Block Table object to load record block info from record section in mdx/mdd file.
|
||||||
|
* Retrived data is stored in an Uint32Array which contains N pairs of (offset_comp, offset_decomp) value,
|
||||||
|
* where N is number of record blocks.
|
||||||
|
*
|
||||||
|
* When looking up a given key for its definition:
|
||||||
|
* 1. Search KEY_INDEX to locate keyword block containing the given key.
|
||||||
|
* 2. Scanning the found keyword block to get its record offset and size.
|
||||||
|
* 3. Search RECORD_BLOCK_TABLE to get record block containing the record.
|
||||||
|
* 4. Load the found record block, using its offset and size to retrieve record content.
|
||||||
|
*
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#record-section
|
||||||
|
*/
|
||||||
|
function createRecordBlockTable() {
|
||||||
|
var pos = 0, // current position
|
||||||
|
arr; // backed Uint32Array
|
||||||
|
return {
|
||||||
|
// Allocate required ArrayBuffer for storing record block table, where len is number of record blocks.
|
||||||
|
alloc: function(len) {
|
||||||
|
arr = new Uint32Array(len * 2);
|
||||||
|
},
|
||||||
|
// Store offset pair value (compressed & decompressed) for a record block
|
||||||
|
// NOTE: offset_comp is absolute offset counted from start of mdx/mdd file.
|
||||||
|
put: function(offset_comp, offset_decomp) {
|
||||||
|
arr[pos++] = offset_comp; arr[pos++] = offset_decomp;
|
||||||
|
},
|
||||||
|
// Given offset of a keyword after decompression, return a record block info containing it, else undefined if not found.
|
||||||
|
find: function(keyAt) {
|
||||||
|
var hi = (arr.length >> 1) - 1, lo = 0, i = (lo + hi) >> 1, val = arr[(i << 1) + 1];
|
||||||
|
|
||||||
|
if (keyAt > arr[(hi << 1) + 1] || keyAt < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (hi - lo <= 1) {
|
||||||
|
if (i < hi) {
|
||||||
|
return {
|
||||||
|
block_no: i,
|
||||||
|
comp_offset: arr[i <<= 1],
|
||||||
|
comp_size: arr[i + 2] - arr[i],
|
||||||
|
decomp_offset:arr[i + 1],
|
||||||
|
decomp_size: arr[i + 3] - arr[i + 1]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(keyAt < val) ? hi = i : lo = i;
|
||||||
|
i = (lo + hi) >> 1;
|
||||||
|
val = arr[(i << 1) + 1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a value of dictionary attribute is true or not.
|
||||||
|
*/
|
||||||
|
function isTrue(v) {
|
||||||
|
v = ((v || false) + '').toLowerCase();
|
||||||
|
return v === 'yes' || v === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a MDict dictionary/resource file (mdx/mdd).
|
||||||
|
* @param file a File/Blob object
|
||||||
|
* @param ext file extension, mdx/mdd
|
||||||
|
* @return a Promise object which will resolve to a lookup function.
|
||||||
|
*/
|
||||||
|
function parse_mdict(file, ext) {
|
||||||
|
|
||||||
|
var KEY_INDEX, // keyword index array
|
||||||
|
RECORD_BLOCK_TABLE = createRecordBlockTable(); // record block table
|
||||||
|
|
||||||
|
var attrs = {}, // storing dictionary attributes
|
||||||
|
_v2, // true if enginge version > 2
|
||||||
|
_bpu, // bytes per unit when converting text size to byte length for text data
|
||||||
|
_tail, // need to skip extra tail bytes after decoding text
|
||||||
|
_decoder, // text decorder
|
||||||
|
|
||||||
|
_decryptors = [false, false],
|
||||||
|
// [keyword_header_decryptor, keyword_index_decryptor], only keyword_index_decryptor is supported
|
||||||
|
|
||||||
|
_searchTextLen, // search NUL to get text length
|
||||||
|
|
||||||
|
_readShort = function(scanner) { return scanner.readUint8(); },
|
||||||
|
// read a "short" number representing kewword text size, 8-bit for version < 2, 16-bit for version >= 2
|
||||||
|
|
||||||
|
_readNum = function(scanner) { return scanner.readInt(); },
|
||||||
|
// Read a number representing offset or data block size, 16-bit for version < 2, 32-bit for version >= 2
|
||||||
|
|
||||||
|
_checksum_v2 = function() {},
|
||||||
|
// Version >= 2.0 only checksum
|
||||||
|
|
||||||
|
_adaptKey = function(key) { return key; },
|
||||||
|
// adapt key by converting to lower case or stripping punctuations according to dictionary attributes (KeyCaseSensitive, StripKey)
|
||||||
|
|
||||||
|
_slice = sliceThen.bind(null, file);
|
||||||
|
// bind sliceThen() with file argument
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Config scanner according to dictionary attributes.
|
||||||
|
*/
|
||||||
|
function config() {
|
||||||
|
attrs.Encoding = attrs.Encoding || 'UTF-16';
|
||||||
|
|
||||||
|
_searchTextLen = (attrs.Encoding === 'UTF-16')
|
||||||
|
? function(dv, offset) {
|
||||||
|
offset = offset;
|
||||||
|
var mark = offset;
|
||||||
|
while (dv.getUint16(offset)) { offset+= _bpu /* scan for \u0000 */ };
|
||||||
|
return offset - mark;
|
||||||
|
} : function(dv, offset) {
|
||||||
|
offset = offset;
|
||||||
|
var mark = offset;
|
||||||
|
while (dv.getUint8(offset++)) { /* scan for NUL */ }
|
||||||
|
return offset - mark - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
_decoder = new TextDecoder(attrs.Encoding || 'UTF-16LE');
|
||||||
|
|
||||||
|
_bpu = (attrs.Encoding === 'UTF-16') ? 2 : 1;
|
||||||
|
|
||||||
|
if (parseInt(attrs.GeneratedByEngineVersion, 10) >= 2.0) {
|
||||||
|
_v2 = true;
|
||||||
|
_tail = _bpu;
|
||||||
|
|
||||||
|
// HUGE dictionary file (>4G) is not supported, take only lower 32-bit
|
||||||
|
_readNum = function(scanner) { return scanner.forward(4), scanner.readInt(); };
|
||||||
|
_readShort = function(scanner) { return scanner.readUint16(); };
|
||||||
|
_checksum_v2 = function(scanner) { return scanner.checksum(); };
|
||||||
|
} else {
|
||||||
|
_tail = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// keyword index decrypted?
|
||||||
|
if (attrs.Encrypted & 0x02) {
|
||||||
|
_decryptors[1] = decrypt;
|
||||||
|
}
|
||||||
|
|
||||||
|
var regexp = MCommon.REGEXP_STRIPKEY[ext];
|
||||||
|
if (isTrue(attrs.KeyCaseSensitive)) {
|
||||||
|
_adaptKey = isTrue(attrs.StripKey)
|
||||||
|
? function(key) { return key.replace(regexp, '$1'); }
|
||||||
|
: function(key) { return key; };
|
||||||
|
} else {
|
||||||
|
_adaptKey = isTrue(attrs.StripKey || (_v2 ? '' : 'yes'))
|
||||||
|
? function(key) { return key.toLowerCase().replace(regexp, '$1'); }
|
||||||
|
: function(key) { return key.toLowerCase(); };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data in current offset from target data ArrayBuffer
|
||||||
|
function Scanner(buf, len) {
|
||||||
|
var offset = 0, dv = new DataView(buf);
|
||||||
|
|
||||||
|
var methods = {
|
||||||
|
// target data size in bytes
|
||||||
|
size: function() { return len || buf.byteLength; },
|
||||||
|
// update offset to new position
|
||||||
|
forward: function(len) { return offset += len; },
|
||||||
|
// return current offset
|
||||||
|
offset: function() { return offset; },
|
||||||
|
|
||||||
|
// MDict file format uses big endian to store number
|
||||||
|
|
||||||
|
// 32-bit unsigned int
|
||||||
|
readInt: function() { return conseq(dv.getUint32(offset, false), this.forward(4)); },
|
||||||
|
readUint16: function() { return conseq(dv.getUint16(offset, false), this.forward(2)); },
|
||||||
|
readUint8: function() { return conseq(dv.getUint8(offset, false), this.forward(1)); },
|
||||||
|
|
||||||
|
// Read a "short" number representing keyword text size, 8-bit for version < 2, 16-bit for version >= 2
|
||||||
|
readShort: function() { return _readShort(this); },
|
||||||
|
// Read a number representing offset or data block size, 16-bit for version < 2, 32-bit for version >= 2
|
||||||
|
readNum: function() { return _readNum(this); },
|
||||||
|
|
||||||
|
readUTF16: function(len) { return conseq(UTF_16LE.decode(new Uint8Array(buf, offset, len)), this.forward(len)); },
|
||||||
|
|
||||||
|
// Read data to an Uint8Array and decode it to text with specified encoding.
|
||||||
|
// Text length in bytes is determined by searching terminated NUL.
|
||||||
|
// NOTE: After decoding the text, it is need to forward extra "tail" bytes according to specified encoding.
|
||||||
|
readText: function() {
|
||||||
|
var len = _searchTextLen(dv, offset);
|
||||||
|
return conseq(_decoder.decode(new Uint8Array(buf, offset, len)), this.forward(len + _bpu));
|
||||||
|
},
|
||||||
|
// Read data to an Uint8Array and decode it to text with specified encoding.
|
||||||
|
// @param len length in basic unit, need to multiply byte per unit to get length in bytes
|
||||||
|
// NOTE: After decoding the text, it is need to forward extra "tail" bytes according to specified encoding.
|
||||||
|
readTextSized: function(len) {
|
||||||
|
len *= _bpu;
|
||||||
|
return conseq(_decoder.decode(new Uint8Array(buf, offset, len)), this.forward(len + _tail));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Skip checksum, just ignore it anyway.
|
||||||
|
checksum: function() { this.forward(4); },
|
||||||
|
// Version >= 2.0 only
|
||||||
|
checksum_v2: function() { return _checksum_v2(this); },
|
||||||
|
|
||||||
|
// Read data block of keyword index, key block or record content.
|
||||||
|
// These data block are maybe in compressed (gzip or lzo) format, while keyword index maybe be encrypted.
|
||||||
|
// @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#compression (with typo mistake)
|
||||||
|
readBlock: function(len, expectedBufSize, decryptor) {
|
||||||
|
var comp_type = dv.getUint8(offset, false); // compression type, 0 = non, 1 = lzo, 2 = gzip
|
||||||
|
if (comp_type === 0) {
|
||||||
|
if (_v2) {
|
||||||
|
this.forward(8); // for version >= 2, skip comp_type (4 bytes with tailing \x00) and checksum (4 bytes)
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
// skip comp_type (4 bytes with tailing \x00) and checksum (4 bytes)
|
||||||
|
offset += 8; len -= 8;
|
||||||
|
var tmp = new Uint8Array(buf, offset, len);
|
||||||
|
if (decryptor) {
|
||||||
|
var passkey = new Uint8Array(8);
|
||||||
|
passkey.set(new Uint8Array(buf, offset - 4, 4)); // key part 1: checksum
|
||||||
|
passkey.set([0x95, 0x36, 0x00, 0x00], 4); // key part 2: fixed data
|
||||||
|
tmp = decryptor(tmp, passkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = comp_type === 2 ? pako.inflate(tmp) : lzo.decompress(tmp, expectedBufSize, 1308672);
|
||||||
|
this.forward(len);
|
||||||
|
return Scanner(tmp.buffer, tmp.length);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Read raw data as Uint8Array from current offset with specified length in bytes
|
||||||
|
readRaw: function(len) {
|
||||||
|
return conseq(new Uint8Array(buf, offset, len), this.forward(len === UNDEFINED ? buf.length - offset : len));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.create(methods);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the first 4 bytes of mdx/mdd file to get length of header_str.
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#file-structure
|
||||||
|
* @param input sliced file (start = 0, length = 4)
|
||||||
|
* @return length of header_str
|
||||||
|
*/
|
||||||
|
function read_file_head(input) {
|
||||||
|
return Scanner(input).readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read header section, parse dictionary attributes and config scanner according to engine version attribute.
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#header-section
|
||||||
|
* @param input sliced file (start = 4, length = len + 48), header string + header section (max length 48)
|
||||||
|
* @param len lenghth of header_str
|
||||||
|
* @return [remained length of header section (header_str and checksum, = len + 4), original input]
|
||||||
|
*/
|
||||||
|
function read_header_sect(input, len) {
|
||||||
|
var scanner = Scanner(input),
|
||||||
|
header_str = scanner.readUTF16(len).replace(/\0$/, ''); // need to remove tailing NUL
|
||||||
|
|
||||||
|
// parse dictionary attributes
|
||||||
|
var xml = parseXml(header_str).querySelector('Dictionary, Library_Data').attributes;
|
||||||
|
|
||||||
|
for (var i = 0, item; i < xml.length; i++) {
|
||||||
|
item = xml.item(i);
|
||||||
|
attrs[item.nodeName] = item.nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs.Encrypted = parseInt(attrs.Encrypted, 10) || 0;
|
||||||
|
|
||||||
|
MCommon.log('dictionary attributes: ', attrs);
|
||||||
|
config();
|
||||||
|
return spreadus(len + 4, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read keyword summary at the begining of keyword section.
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#keyword-section
|
||||||
|
* @param input sliced file, same as input passed to read_header_sect()
|
||||||
|
* @param offset start position of keyword section in sliced file, equals to length of header string plus checksum.\
|
||||||
|
* @return keyword_sect object
|
||||||
|
*/
|
||||||
|
function read_keyword_summary(input, offset) {
|
||||||
|
var scanner = Scanner(input);
|
||||||
|
scanner.forward(offset);
|
||||||
|
return {
|
||||||
|
num_blocks: scanner.readNum(),
|
||||||
|
num_entries: scanner.readNum(),
|
||||||
|
key_index_decomp_len: _v2 && scanner.readNum(), // Ver >= 2.0 only
|
||||||
|
key_index_comp_len: scanner.readNum(),
|
||||||
|
key_blocks_len: scanner.readNum(),
|
||||||
|
chksum: scanner.checksum_v2(),
|
||||||
|
// extra field
|
||||||
|
len: scanner.offset() - offset, // actual length of keyword section, varying with engine version attribute
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read keyword index part of keyword section.
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#keyword-header-encryption
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#keyword-index
|
||||||
|
* @param input sliced file, remained part of keyword section after keyword summary which can also be used to read following key blocks.
|
||||||
|
* @param keyword_summary
|
||||||
|
* @return [keyword_summary, array of keyword index]
|
||||||
|
*/
|
||||||
|
function read_keyword_index(input, keyword_summary) {
|
||||||
|
var scanner = Scanner(input).readBlock(keyword_summary.key_index_comp_len, keyword_summary.key_index_decomp_len, _decryptors[1]),
|
||||||
|
keyword_index = Array(keyword_summary.num_blocks),
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
for (var i = 0, size; i < keyword_summary.num_blocks; i++) {
|
||||||
|
keyword_index[i] = {
|
||||||
|
num_entries: conseq(scanner.readNum(), size = scanner.readShort()),
|
||||||
|
// UNUSED, can be ignored
|
||||||
|
// first_size: size = scanner.readShort(),
|
||||||
|
first_word: conseq(scanner.readTextSized(size), size = scanner.readShort()),
|
||||||
|
// UNUSED, can be ignored
|
||||||
|
// last_size: size = scanner.readShort(),
|
||||||
|
last_word: scanner.readTextSized(size),
|
||||||
|
comp_size: size = scanner.readNum(),
|
||||||
|
decomp_size: scanner.readNum(),
|
||||||
|
// extra fields
|
||||||
|
offset: offset, // offset of the first byte for the target key block in mdx/mdd file
|
||||||
|
index: i // index of this key index, used to search previous/next block
|
||||||
|
};
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
return spreadus(keyword_summary, keyword_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read keyword entries inside a keyword block and fill KEY_TABLE.
|
||||||
|
* @param scanner scanner object to read key entries, which starts at begining of target key block
|
||||||
|
* @param kdx corresponding keyword index object
|
||||||
|
* NOTE: no need to read keyword block anymore, for debug only.
|
||||||
|
*/
|
||||||
|
function read_key_block(scanner, kdx) {
|
||||||
|
var scanner = scanner.readBlock(kdx.comp_size, kdx.decomp_size);
|
||||||
|
for (var i = 0; i < kdx.num_entries; i++) {
|
||||||
|
// scanner.readNum(); scanner.readText();
|
||||||
|
var kk = [scanner.readNum(), scanner.readText()];
|
||||||
|
// console.log(scanner.readNum(), scanner.readText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delay to scan key table, for debug onyl.
|
||||||
|
* @param slicedKeyBlock a promise object which will resolve to an ArrayBuffer containing keyword blocks
|
||||||
|
* sliced from mdx/mdd file.
|
||||||
|
* @param num_entries number of keyword entries
|
||||||
|
* @param keyword_index array of keyword index
|
||||||
|
* @param delay time to delay for scanning key table
|
||||||
|
*/
|
||||||
|
function willScanKeyTable(slicedKeyBlock, num_entries, keyword_index, delay) {
|
||||||
|
slicedKeyBlock.delay(delay).then(function (input) {
|
||||||
|
MCommon.log('scan key table...');
|
||||||
|
var scanner = Scanner(input);
|
||||||
|
for (var i = 0, size = keyword_index.length; i < size; i++) {
|
||||||
|
read_key_block(scanner, keyword_index[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
MCommon.log('KEY_TABLE loaded.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read record summary at the begining of record section.
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#record-section
|
||||||
|
* @param input sliced file, start = begining of record section, length = 32 (max length of record summary)
|
||||||
|
* @param pos begining of record section
|
||||||
|
* @returj record summary object
|
||||||
|
*/
|
||||||
|
function read_record_summary(input, pos) {
|
||||||
|
var scanner = Scanner(input),
|
||||||
|
record_summary = {
|
||||||
|
num_blocks: scanner.readNum(),
|
||||||
|
num_entries: scanner.readNum(),
|
||||||
|
index_len: scanner.readNum(),
|
||||||
|
blocks_len: scanner.readNum(),
|
||||||
|
// extra field
|
||||||
|
len: scanner.offset(), // actual length of record section (excluding record block index), varying with engine version attribute
|
||||||
|
};
|
||||||
|
|
||||||
|
// start position of record block from head of mdx/mdd file
|
||||||
|
record_summary.block_pos = pos + record_summary.index_len + record_summary.len;
|
||||||
|
|
||||||
|
return record_summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read record block index part in record section, and fill RECORD_BLOCK_TABLE
|
||||||
|
* @see https://github.com/zhansliu/writemdict/blob/master/fileformat.md#record-section
|
||||||
|
* @param input sliced file, start = begining of record block index, length = record_summary.index_len
|
||||||
|
* @param record_summary record summary object
|
||||||
|
*/
|
||||||
|
function read_record_block(input, record_summary) {
|
||||||
|
var scanner = Scanner(input),
|
||||||
|
size = record_summary.num_blocks,
|
||||||
|
record_index = Array(size),
|
||||||
|
p0 = record_summary.block_pos,
|
||||||
|
p1 = 0;
|
||||||
|
|
||||||
|
RECORD_BLOCK_TABLE.alloc(size + 1);
|
||||||
|
for (var i = 0, rdx; i < size; i++) {
|
||||||
|
record_index[i] = rdx = {
|
||||||
|
comp_size: scanner.readNum(),
|
||||||
|
decomp_size: scanner.readNum()
|
||||||
|
};
|
||||||
|
RECORD_BLOCK_TABLE.put(p0, p1);
|
||||||
|
p0 += rdx.comp_size;
|
||||||
|
p1 += rdx.decomp_size;
|
||||||
|
}
|
||||||
|
RECORD_BLOCK_TABLE.put(p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read definition in text for given keyinfo object.
|
||||||
|
* @param input record block sliced from the file
|
||||||
|
* @param block record block index
|
||||||
|
* @param keyinfo a object with property of record's offset and optional size for the given keyword
|
||||||
|
* @return definition in text
|
||||||
|
*/
|
||||||
|
function read_definition(input, block, keyinfo) {
|
||||||
|
var scanner = Scanner(input).readBlock(block.comp_size, block.decomp_size);
|
||||||
|
scanner.forward(keyinfo.offset - block.decomp_offset);
|
||||||
|
return scanner.readText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Following link to find actual definition of keyword.
|
||||||
|
* @param definition maybe starts with "@@@LINK=" which links to another keyword
|
||||||
|
* @param lookup search function
|
||||||
|
* @return resolved actual definition
|
||||||
|
*/
|
||||||
|
function followLink(definition, lookup) {
|
||||||
|
return (definition.substring(0, 8) !== '@@@LINK=')
|
||||||
|
? definition
|
||||||
|
: lookup(definition.substring(8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read content in ArrayBuffer for give keyinfo object
|
||||||
|
* @param input record block sliced from the file
|
||||||
|
* @param block record block index
|
||||||
|
* @param keyinfo a object with property of record's offset and optional size for the given keyword
|
||||||
|
* @return an ArrayBuffer containing resource of image/audio/css/font etc.
|
||||||
|
*/
|
||||||
|
function read_object(input, block, keyinfo) {
|
||||||
|
if (input.byteLength > 0) {
|
||||||
|
var scanner = Scanner(input).readBlock(block.comp_size, block.decomp_size);
|
||||||
|
scanner.forward(keyinfo.offset - block.decomp_offset);
|
||||||
|
return scanner.readRaw(keyinfo.size);
|
||||||
|
} else {
|
||||||
|
throw '* OUT OF FILE RANGE * ' + keyinfo + ' @offset=' + block.comp_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find word definition for given keyinfo object.
|
||||||
|
* @param keyinfo a object with property of record's offset and optional size for the given keyword
|
||||||
|
* @return a promise object which will resolve to definition in text. Link to other keyword is followed to get actual definition.
|
||||||
|
*/
|
||||||
|
function findWord(keyinfo) {
|
||||||
|
var block = RECORD_BLOCK_TABLE.find(keyinfo.offset);
|
||||||
|
return _slice(block.comp_offset, block.comp_size)
|
||||||
|
.exec(read_definition, block, keyinfo)
|
||||||
|
.spread(function (definition) { return resolve(followLink(definition, LOOKUP.mdx)); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find resource (image, sound etc.) for given keyinfo object.
|
||||||
|
* @param keyinfo a object with property of record's offset and optional size for the given keyword
|
||||||
|
* @return a promise object which will resolve to an ArrayBuffer containing resource of image/audio/css/font etc.
|
||||||
|
* TODO: Follow link, maybe it's too expensive and a rarely used feature?
|
||||||
|
*/
|
||||||
|
function findResource(keyinfo) {
|
||||||
|
var block = RECORD_BLOCK_TABLE.find(keyinfo.offset);
|
||||||
|
return _slice(block.comp_offset, block.comp_size)
|
||||||
|
.exec(read_object, block, keyinfo)
|
||||||
|
.spread(function (blob) { return resolve(blob); });
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------------------
|
||||||
|
// Implementation for look-up
|
||||||
|
//------------------------------------------------------------------------------------------------
|
||||||
|
var slicedKeyBlock,
|
||||||
|
_cached_keys, // cache latest keys
|
||||||
|
_trail, // store latest visited record block & position when search for candidate keys
|
||||||
|
mutual_ticket = 0; // a oneway increased ticket used to cancel unfinished pattern match
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce the key index array to an element which contains or is the nearest one matching a given phrase.
|
||||||
|
*/
|
||||||
|
function reduce(arr, phrase) {
|
||||||
|
var len = arr.length;
|
||||||
|
if (len > 1) {
|
||||||
|
len = len >> 1;
|
||||||
|
return phrase > _adaptKey(arr[len - 1].last_word)
|
||||||
|
? reduce(arr.slice(len), phrase)
|
||||||
|
: reduce(arr.slice(0, len), phrase);
|
||||||
|
} else {
|
||||||
|
return arr[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce the array to index of an element which contains or is the nearest one matching a given phrase.
|
||||||
|
*/
|
||||||
|
function shrink(arr, phrase) {
|
||||||
|
var len = arr.length, sub;
|
||||||
|
if (len > 1) {
|
||||||
|
len = len >> 1;
|
||||||
|
var key = _adaptKey(arr[len]);
|
||||||
|
if (phrase < key) {
|
||||||
|
sub = arr.slice(0, len);
|
||||||
|
sub.pos = arr.pos;
|
||||||
|
} else {
|
||||||
|
sub = arr.slice(len);
|
||||||
|
sub.pos = (arr.pos || 0) + len;
|
||||||
|
}
|
||||||
|
return shrink(sub, phrase);
|
||||||
|
} else {
|
||||||
|
return (arr.pos || 0) + (phrase <= _adaptKey(arr[0]) ? 0 : 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load keys for a keyword index object from mdx/mdd file.
|
||||||
|
* @param kdx keyword index object
|
||||||
|
*/
|
||||||
|
function loadKeys(kdx) {
|
||||||
|
if (_cached_keys && _cached_keys.pilot === kdx.first_word) {
|
||||||
|
return resolve(_cached_keys.list);
|
||||||
|
} else {
|
||||||
|
return slicedKeyBlock.then(function(input) {
|
||||||
|
var scanner = Scanner(input), list = Array(kdx.num_entries);
|
||||||
|
scanner.forward(kdx.offset);
|
||||||
|
scanner = scanner.readBlock(kdx.comp_size, kdx.decomp_size);
|
||||||
|
|
||||||
|
for (var i = 0; i < kdx.num_entries; i++) {
|
||||||
|
var offset = scanner.readNum();
|
||||||
|
list[i] = new Object(scanner.readText());
|
||||||
|
list[i].offset = offset;
|
||||||
|
if (i > 0) {
|
||||||
|
list[i - 1].size = offset - list[i - 1].offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_cached_keys = {list: list, pilot: kdx.first_word};
|
||||||
|
return list;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for the first keyword match given phrase.
|
||||||
|
*/
|
||||||
|
function seekVanguard(phrase) {
|
||||||
|
phrase = _adaptKey(phrase);
|
||||||
|
var kdx = reduce(KEY_INDEX, phrase);
|
||||||
|
|
||||||
|
// look back for the first record block containing keyword for the specified phrase
|
||||||
|
if (phrase <= _adaptKey(kdx.last_word)) {
|
||||||
|
var index = kdx.index - 1, prev;
|
||||||
|
while (prev = KEY_INDEX[index]) {
|
||||||
|
if (_adaptKey(prev.last_word) !== _adaptKey(kdx.last_word)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
kdx = prev;
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loadKeys(kdx).then(function (list) {
|
||||||
|
var idx = shrink(list, phrase);
|
||||||
|
// look back for the first matched keyword position
|
||||||
|
while (idx > 0) {
|
||||||
|
if (_adaptKey(list[--idx]) !== _adaptKey(phrase)) {
|
||||||
|
idx++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [kdx, Math.min(idx, list.length - 1), list];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: have to restrict max count to improve response
|
||||||
|
/**
|
||||||
|
* Append more to word list according to a filter or expected size.
|
||||||
|
*/
|
||||||
|
function appendMore(word, list, nextKdx, expectedSize, filter, ticket) {
|
||||||
|
if (ticket !== mutual_ticket) {
|
||||||
|
throw 'force terminated';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
if (_trail.count < expectedSize && nextKdx && nextKdx.first_word.substr(0, word.length) === word) {
|
||||||
|
return loadKeys(nextKdx).delay(30).then(function(more) {
|
||||||
|
MCommon.log(nextKdx);
|
||||||
|
_trail.offset = 0;
|
||||||
|
_trail.block = nextKdx.index;
|
||||||
|
Array.prototype.push.apply(list, more.filter(filter, _trail));
|
||||||
|
return appendMore(word, list, KEY_INDEX[nextKdx.index + 1], expectedSize, filter, ticket);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (list.length === 0) {
|
||||||
|
_trail.exhausted = true;
|
||||||
|
}
|
||||||
|
return resolve(list);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var shortage = expectedSize - list.length;
|
||||||
|
if (shortage > 0 && nextKdx) {
|
||||||
|
console.log('go next', nextKdx);
|
||||||
|
_trail.block = nextKdx.index;
|
||||||
|
return loadKeys(nextKdx).then(function(more) {
|
||||||
|
_trail.offset = 0;
|
||||||
|
_trail.pos = Math.min(shortage, more.length);
|
||||||
|
Array.prototype.push.apply(list, more.slice(0, shortage));
|
||||||
|
console.log('$$ ' + more[shortage - 1], shortage);
|
||||||
|
return appendMore(word, list, KEY_INDEX[nextKdx.index + 1], expectedSize, filter, ticket);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (_trail.pos > expectedSize) {
|
||||||
|
_trail.pos = expectedSize;
|
||||||
|
}
|
||||||
|
list = list.slice(0, expectedSize);
|
||||||
|
_trail.count = list.length;
|
||||||
|
_trail.total += _trail.count;
|
||||||
|
return resolve(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function followUp() {
|
||||||
|
var kdx = KEY_INDEX[_trail.block];
|
||||||
|
return loadKeys(kdx).then(function (list) {
|
||||||
|
return [kdx, Math.min(_trail.offset + _trail.pos, list.length - 1), list];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchKeys(phrase, expectedSize, follow) {
|
||||||
|
expectedSize = Math.max(expectedSize || 0, 10);
|
||||||
|
var str = phrase.trim().toLowerCase(),
|
||||||
|
m = /([^?*]+)[?*]+/.exec(str),
|
||||||
|
word;
|
||||||
|
if (m) {
|
||||||
|
word = m[1];
|
||||||
|
var wildcard = new RegExp('^' + str.replace(/([\.\\\+\[\^\]\$\(\)])/g, '\\$1').replace(/\*+/g, '.*').replace(/\?/g, '.') + '$'),
|
||||||
|
tester = phrase[phrase.length - 1] === ' '
|
||||||
|
? function(s) { return wildcard.test(s); }
|
||||||
|
: function(s) { return wildcard.test(s) && !/ /.test(s); },
|
||||||
|
filter = function (s, i) {
|
||||||
|
if (_trail.count < expectedSize && tester(s)) {
|
||||||
|
_trail.count++;
|
||||||
|
_trail.total++;
|
||||||
|
_trail.pos = i + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
word = phrase.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_trail && _trail.phrase !== phrase) {
|
||||||
|
follow = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (follow && _trail && _trail.exhausted) {
|
||||||
|
return resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var startFrom = follow && _trail ? followUp() : seekVanguard(word);
|
||||||
|
|
||||||
|
return startFrom.spread(function(kdx, idx, list) {
|
||||||
|
console.log('start ', kdx);
|
||||||
|
list = list.slice(idx);
|
||||||
|
_trail = {phrase: phrase,
|
||||||
|
block: kdx.index,
|
||||||
|
offset: idx,
|
||||||
|
pos: list.length,
|
||||||
|
count: 0,
|
||||||
|
total: follow ? _trail && _trail.total || 0 : 0
|
||||||
|
};
|
||||||
|
if (filter) {
|
||||||
|
list = list.filter(filter, _trail);
|
||||||
|
}
|
||||||
|
return appendMore(word, list, KEY_INDEX[kdx.index + 1], expectedSize, filter, ++mutual_ticket)
|
||||||
|
.then(function(result) {
|
||||||
|
if (_trail.block === KEY_INDEX.length - 1) {
|
||||||
|
if (_trail.offset + _trail.pos >= KEY_INDEX[_trail.block].num_entries) {
|
||||||
|
_trail.exhausted = true;
|
||||||
|
console.log('EXHAUSTED!!!!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('trail: ', _trail);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match the first element in list with given offset.
|
||||||
|
*/
|
||||||
|
function matchOffset(list, offset) {
|
||||||
|
return list.some(function(el) { return el.offset === offset ? list = [el] : false; }) ? list : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup functions
|
||||||
|
var LOOKUP = {
|
||||||
|
/**
|
||||||
|
* @param query
|
||||||
|
* String
|
||||||
|
* {phrase: .., max: .., follow: true} object
|
||||||
|
*/
|
||||||
|
mdx: function(query) {
|
||||||
|
if (typeof query === 'string' || query instanceof String) {
|
||||||
|
_trail = null;
|
||||||
|
var word = query.trim().toLowerCase(), offset = query.offset;
|
||||||
|
|
||||||
|
return seekVanguard(word).spread(function(kdx, idx, list) {
|
||||||
|
list = list.slice(idx);
|
||||||
|
if (offset !== UNDEFINED) {
|
||||||
|
list = matchOffset(list, offset);
|
||||||
|
} else {
|
||||||
|
list = list.filter(function(el) { return el.toLowerCase() === word; });
|
||||||
|
}
|
||||||
|
return harvest(list.map(findWord));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return matchKeys(query.phrase, query.max, query.follow);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: chain multiple mdd file
|
||||||
|
mdd: function(phrase) {
|
||||||
|
var word = phrase.trim().toLowerCase();
|
||||||
|
word = '\\' + word.replace(/(^[/\\])|([/]$)/, '');
|
||||||
|
word = word.replace(/\//g, '\\');
|
||||||
|
return seekVanguard(word).spread(function(kdx, idx, list) {
|
||||||
|
return list.slice(idx).filter(function(one) {
|
||||||
|
return one.toLowerCase() === word;
|
||||||
|
});
|
||||||
|
}).then(function(candidates) {
|
||||||
|
if (candidates.length === 0) {
|
||||||
|
throw '*RESOURCE NOT FOUND* ' + phrase;
|
||||||
|
} else {
|
||||||
|
return findResource(candidates[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------------------------------
|
||||||
|
// start to load mdx/mdd file
|
||||||
|
// ------------------------------------------
|
||||||
|
MCommon.log('start to load ' + file.name);
|
||||||
|
|
||||||
|
var pos = 0;
|
||||||
|
|
||||||
|
// read first 4 bytes to get header length
|
||||||
|
return _slice(pos, 4).exec(read_file_head).spread(function(len) {
|
||||||
|
pos += 4; // start of header string in header section
|
||||||
|
return _slice(pos, len + 48)
|
||||||
|
.exec(read_header_sect, len);
|
||||||
|
|
||||||
|
}).spread(function(header_remain_len, input) {
|
||||||
|
pos += header_remain_len; // start of keyword section
|
||||||
|
return read_keyword_summary(input, header_remain_len);
|
||||||
|
|
||||||
|
}).then(function(keyword_summary) { MCommon.log(keyword_summary);
|
||||||
|
pos += keyword_summary.len; // start of key index in keyword section
|
||||||
|
return _slice(pos, keyword_summary.key_index_comp_len)
|
||||||
|
.exec(read_keyword_index, keyword_summary);
|
||||||
|
|
||||||
|
}).spread(function (keyword_summary, keyword_index) {
|
||||||
|
pos += keyword_summary.key_index_comp_len; // start of keyword block in keyword section
|
||||||
|
slicedKeyBlock = _slice(pos, keyword_summary.key_blocks_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Now it's fast enough to look up word without key table, which scans keyword from the specified key blocks in an effcient way.
|
||||||
|
// No need to scan the whole key table in ahead.
|
||||||
|
willScanKeyTable(slicedKeyBlock, keyword_summary.num_entries, keyword_index, 00);
|
||||||
|
// */
|
||||||
|
|
||||||
|
pos += keyword_summary.key_blocks_len; // start of record section
|
||||||
|
|
||||||
|
KEY_INDEX = keyword_index;
|
||||||
|
|
||||||
|
}).then(function () {
|
||||||
|
return _slice(pos, 32)
|
||||||
|
.exec(read_record_summary, pos);
|
||||||
|
|
||||||
|
}).spread(function (record_summary) { MCommon.log(record_summary);
|
||||||
|
pos += record_summary.len; // start of record blocks in record section
|
||||||
|
return _slice(pos, record_summary.index_len)
|
||||||
|
.exec(read_record_block, record_summary);
|
||||||
|
|
||||||
|
}).spread(function() { MCommon.log('-- parse done --', file.name);
|
||||||
|
// resolve and return lookup() function according to file extension (mdx/mdd)
|
||||||
|
LOOKUP[ext].description = attrs.Description;
|
||||||
|
return resolve(LOOKUP[ext]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// END OF parse_mdict()
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a set of files which will be parsed as MDict dictionary & resource (mdx/mdd).
|
||||||
|
*/
|
||||||
|
return function load(files) {
|
||||||
|
var resources = [];
|
||||||
|
Array.prototype.forEach.call(files, function(f) {
|
||||||
|
var ext = MCommon.getExtension(f.name, 'mdx');
|
||||||
|
|
||||||
|
resources.push(resources[ext] = parse_mdict(f, ext));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(resources)
|
||||||
|
.then(function() { return resolve(resources); });
|
||||||
|
};
|
||||||
|
|
||||||
|
}());
|
200
en/searchurl/mdict/mdict-renderer.js
Normal file
200
en/searchurl/mdict/mdict-renderer.js
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
//define(['jquery', 'bluebird', 'speex', 'pcmdata', 'bitstring'], factory);
|
||||||
|
//$, Promise, SpeexLib, PCMDataLib
|
||||||
|
/**
|
||||||
|
* Usage:
|
||||||
|
* var fileList = ...; // FileList object
|
||||||
|
* var word = ...; // word for lookup
|
||||||
|
* require(['mdict-parser', 'mdict-renderer'], function(MParser, MRenderer) {
|
||||||
|
* MParser(fileList).then(function(resources) {
|
||||||
|
* var mdict = MRenderer(resources),
|
||||||
|
* dict_desc = resources.description.mdx;
|
||||||
|
* mdict.lookup(word).then(function($content) {
|
||||||
|
* // use $content to display result
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
var MRenderer = (function () {
|
||||||
|
var MIME = {
|
||||||
|
'css': 'text/css',
|
||||||
|
'img': 'image',
|
||||||
|
'jpg': 'image/jpeg',
|
||||||
|
'png': 'image/png',
|
||||||
|
'spx': 'audio/x-speex',
|
||||||
|
'wav': 'audio/wav',
|
||||||
|
'mp3': 'audio/mp3',
|
||||||
|
'js' : 'text/javascript'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getExtension(filename, defaultExt) {
|
||||||
|
return /(?:\.([^.]+))?$/.exec(filename)[1] || defaultExt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: revoke unused resource, LRU
|
||||||
|
// TODO: support for word variation
|
||||||
|
return function createRenderer(resources) {
|
||||||
|
|
||||||
|
var cache = (function createCache(mdd) {
|
||||||
|
var repo = {};
|
||||||
|
|
||||||
|
function get(id, load) {
|
||||||
|
var entry = repo[id];
|
||||||
|
if (!entry) {
|
||||||
|
repo[id] = entry = new Promise(function(resolve) {
|
||||||
|
var will = mdd.then(function(lookup) {
|
||||||
|
console.log('lookup: ' + id);
|
||||||
|
return lookup(id);
|
||||||
|
}).then(load)
|
||||||
|
.then(function(url) { resolve(url); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {get: get};
|
||||||
|
})(resources['mdd']);
|
||||||
|
|
||||||
|
function loadData(mime, data) {
|
||||||
|
var blob = new Blob([data], {type: mime});
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAudio(ext, data) {
|
||||||
|
if (ext === 'spx') {
|
||||||
|
var blob = decodeSpeex(String.fromCharCode.apply(null, data));
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
} else { // 'spx'
|
||||||
|
return loadData(MIME[ext] || 'audio', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LRU cache: remove oldest one only after rendering.
|
||||||
|
function replaceImage(index, img) {
|
||||||
|
var $img = $(img);
|
||||||
|
var src = $img.attr('src'), m = /^file:\/\/(.*)/.exec(src);
|
||||||
|
if (m) { src = m[1]; }
|
||||||
|
cache.get(src, loadData.bind(null, MIME['img']))
|
||||||
|
.then(function(url) {
|
||||||
|
$img.attr({src: url, src_: src});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function playAudio(e, $a) {
|
||||||
|
($a || $(this)).find('audio')[0].play();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderAudio() {
|
||||||
|
var $a = $(this);
|
||||||
|
if ($a.attr('href_')) {
|
||||||
|
playAudio($a);
|
||||||
|
} else {
|
||||||
|
var href = $a.attr('href'), res = href.substring(8);
|
||||||
|
var ext = getExtension(res, 'wav');
|
||||||
|
cache.get(res, loadAudio.bind(null, ext))
|
||||||
|
.then(function(url) {
|
||||||
|
$a.append($('<audio>').attr({src: url, src_: href})).on('click', playAudio);
|
||||||
|
setTimeout(playAudio.bind($a));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceCss(index, link) {
|
||||||
|
var $link = $(link);
|
||||||
|
var href = $link.attr('href');
|
||||||
|
cache.get(href, loadData.bind(null, MIME['css']))
|
||||||
|
.then(function(url) {
|
||||||
|
// $link.attr({href: url, href_: href});
|
||||||
|
// TODO: Limit scope of embedded styles provide by mdd file
|
||||||
|
// TODO: use shadow dom for Chrome
|
||||||
|
// TODO: use scoped style for Firefox
|
||||||
|
$link.replaceWith($('<style scoped>', {src_: href}).text('@import url("' + url + '")'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function injectJS(index, el) {
|
||||||
|
var $el = $(el);
|
||||||
|
var src = $el.attr('src');
|
||||||
|
cache.get(src, loadData.bind(null, MIME['js']))
|
||||||
|
.then(function(url) {
|
||||||
|
$el.remove();
|
||||||
|
$.ajax({url: url, dataType: 'script', cache: true});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeSpeex(file) {
|
||||||
|
var ogg = new Ogg(file, {file: true});
|
||||||
|
ogg.demux();
|
||||||
|
|
||||||
|
var header = Speex.parseHeader(ogg.frames[0]);
|
||||||
|
console.log(header);
|
||||||
|
|
||||||
|
var comment = new SpeexComment(ogg.frames[1]);
|
||||||
|
console.log(comment.data);
|
||||||
|
|
||||||
|
var spx = new Speex({
|
||||||
|
quality: 8,
|
||||||
|
mode: header.mode,
|
||||||
|
rate: header.rate
|
||||||
|
});
|
||||||
|
|
||||||
|
var waveData = PCMData.encode({
|
||||||
|
sampleRate: header.rate,
|
||||||
|
channelCount: header.nb_channels,
|
||||||
|
bytesPerSample: 2,
|
||||||
|
data: spx.decode(ogg.bitstream(), ogg.segments)
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Blob([Speex.util.str2ab(waveData)], {type: "audio/wav"});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render($content) {
|
||||||
|
if (resources['mdd']) {
|
||||||
|
$content.find('img[src]').each(replaceImage);
|
||||||
|
|
||||||
|
$content.find('link[rel=stylesheet]').each(replaceCss);
|
||||||
|
|
||||||
|
$content.find('script[src]').each(injectJS);
|
||||||
|
|
||||||
|
$content.find('a[href^="sound://"]').on('click', renderAudio);
|
||||||
|
|
||||||
|
setTimeout(function() { $('#definition *').trigger('resize'); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve entry:// link dynamically in mdict.js
|
||||||
|
// // rewrite in-page link
|
||||||
|
// $content.find('a[href^="entry://"]').each(function() {
|
||||||
|
// var $el = $(this), href = $el.attr('href');
|
||||||
|
// if (href.match('#')) {
|
||||||
|
// $el.attr('href', href.substring(8));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lookup: function lookup(query) {
|
||||||
|
return (resources['mdx'] || resources['mdd'])
|
||||||
|
.then(function (lookup) {
|
||||||
|
return lookup(query);
|
||||||
|
}).then(function (definitions) {
|
||||||
|
console.log('lookup done!');
|
||||||
|
var html = definitions.reduce(function(prev, txt) {
|
||||||
|
return prev + '<p></p>' + txt;
|
||||||
|
}, '<p>' + definitions.length + ' entry(ies) </p>');
|
||||||
|
return Promise.resolve(render($('<div>').html(html)));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
search: function (query) {
|
||||||
|
return resources['mdx'].then(function(lookup) {
|
||||||
|
return lookup(query);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
render: render,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
206
en/searchurl/mdict/mdict.html
Normal file
206
en/searchurl/mdict/mdict.html
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-us">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>mdict</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css">
|
||||||
|
<link rel="stylesheet" type="text/css" href="selectize.default.css" media="screen">
|
||||||
|
<style>
|
||||||
|
#btnLookup {
|
||||||
|
border: none;
|
||||||
|
height: 36px;
|
||||||
|
font-size: 12pt;
|
||||||
|
font-weight: bold;
|
||||||
|
vertical-align: top;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnLookup:not([disabled]) {
|
||||||
|
background: #1A4FDD;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#word + .selectize-control {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 18em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/fengdh/mdict-js/selectize.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/nodeca/pako/dist/pako_inflate.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/bluebird@3.7.2/js/browser/bluebird.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/torappinfo/uweb/en/searchurl/mdict/ripemd128.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/torappinfo/uweb/en/searchurl/mdict/mdict-common.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/torappinfo/uweb/en/searchurl/mdict/mdict-parser.min.js"></script>
|
||||||
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/torappinfo/uweb/en/searchurl/mdict/mdict-renderer.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
Choose a dictionary file (*.mdx + optional *.mdd): <input id="dictfile" type="file" multiple>
|
||||||
|
<p>
|
||||||
|
<input id="word" type="text" value="">
|
||||||
|
<input id="btnLookup" type="button" value="look up" disabled="false">
|
||||||
|
<div id="definition">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//file:///...mdictstem?app=....html#word=
|
||||||
|
var dictinput = document.getElementById('dictfile');
|
||||||
|
var wordinput = document.getElementById('word');
|
||||||
|
var btnLookup = document.getElementById('btnLookup');
|
||||||
|
var definition = document.getElementById('definition');
|
||||||
|
|
||||||
|
window.onhashchange = function() {
|
||||||
|
let v = location.hash.substring(6);
|
||||||
|
if(v.length>0){
|
||||||
|
wordinput.value = v;
|
||||||
|
//wordinput.dispatchEvent(new Event('change', {'bubbles': true}));
|
||||||
|
btnLookup.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let v = decodeURIComponent(location.hash.substring(6));
|
||||||
|
wordinput.value = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(wordinput).selectize({maxItems: 1});
|
||||||
|
function accept(e) {
|
||||||
|
var fileList = $(e.target).prop('files');
|
||||||
|
$(btnLookup).attr('disabled', true);
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
$(btnLookup).addClass('stripes');
|
||||||
|
$(wordinput).on('keyup', function(e) { e.which === 13
|
||||||
|
&& $(btnLookup).click(); });
|
||||||
|
MParser(fileList).then(function(resources) {
|
||||||
|
var mdict = MRenderer(resources);
|
||||||
|
function doSearch(phrase, offset) {
|
||||||
|
console.log(phrase + '');
|
||||||
|
mdict.lookup(phrase, offset).then(function($content) {
|
||||||
|
$(definition).empty().append($content.contents());
|
||||||
|
console.log('--');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$(btnLookup)
|
||||||
|
.attr('disabled', false)
|
||||||
|
.off('.#mdict')
|
||||||
|
.on('click.#mdict', function() {
|
||||||
|
doSearch($(wordinput).val());
|
||||||
|
}).click();
|
||||||
|
|
||||||
|
$(wordinput)[0].selectize.destroy();
|
||||||
|
|
||||||
|
$(wordinput).selectize({
|
||||||
|
plugins: ['restore_on_backspace'],
|
||||||
|
maxItems: 1,
|
||||||
|
maxOptions: 1 << 20,
|
||||||
|
valueField: 'value',
|
||||||
|
labelField: 'word',
|
||||||
|
searchField: 'word',
|
||||||
|
delimiter: '~~',
|
||||||
|
loadThrottle: 10,
|
||||||
|
create: function(v, callback) {
|
||||||
|
return callback({word: v, value: v});
|
||||||
|
},
|
||||||
|
createOnBlur: true,
|
||||||
|
closeAfterSelect: true,
|
||||||
|
allowEmptyOption: true,
|
||||||
|
score: function(search) {
|
||||||
|
var score =
|
||||||
|
this.getScoreFunction(search);
|
||||||
|
return function(item) {
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
load: function(query, callback) {
|
||||||
|
var self = this;
|
||||||
|
if (!query.length) {
|
||||||
|
this.clearOptions();
|
||||||
|
this.refreshOptions();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
mdict.search({phrase: query, max: 5000}).then(function(list) {
|
||||||
|
// console.log(list.join(', '));
|
||||||
|
// TODO: filter candidate keyword starting with "_"
|
||||||
|
list = list.map(function(v) {
|
||||||
|
return {word: v, value: v.offset};
|
||||||
|
});
|
||||||
|
self.clearOptions();
|
||||||
|
callback(list);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onChange: function(value) {
|
||||||
|
var item = this.options[value];
|
||||||
|
if (item) {
|
||||||
|
var value = item.word;
|
||||||
|
doSearch(value, value.offset);
|
||||||
|
$(wordinput).val(value);
|
||||||
|
} else {
|
||||||
|
$(definition).empty();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}).catch(err => alert(err)) ;
|
||||||
|
} else {
|
||||||
|
$(btnLookup).attr('disabled', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// jump to word with link started with "entry://"
|
||||||
|
// TODO: have to ignore in-page jump
|
||||||
|
$(definition).on('click', 'a', function(e) {
|
||||||
|
var href = $(this).attr('href');
|
||||||
|
if (href && href.substring(0, 8) === 'entry://') {
|
||||||
|
var word = href.substring(8);
|
||||||
|
// TODO: remove '#' to get jump target
|
||||||
|
if (word.charAt(0) !== '#') {
|
||||||
|
word = word.replace(/(^[/\\])|([/]$)/, '');
|
||||||
|
|
||||||
|
$(wordinput).val(word);
|
||||||
|
$(btnLookup).click();
|
||||||
|
} else {
|
||||||
|
var currentUrl = location.href;
|
||||||
|
location.href = word; //Go to the target element.
|
||||||
|
history.replaceState(null,null,currentUrl);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$(dictinput).on('change', accept);
|
||||||
|
|
||||||
|
{
|
||||||
|
let path = location.pathname;
|
||||||
|
let iSlash = path.lastIndexOf('/');
|
||||||
|
let filenames=path.substring(iSlash+1);
|
||||||
|
{
|
||||||
|
let iDot = filenames.indexOf('.');
|
||||||
|
if(iDot>0)
|
||||||
|
document.getElementsByTagName("link")[0].href =
|
||||||
|
filenames.substring(0,iDot)+"css";
|
||||||
|
}
|
||||||
|
window.onload = function(){
|
||||||
|
if(filenames.length>0){
|
||||||
|
let clickurl = "i:5fdictinput.click():"+filenames;
|
||||||
|
location.href=clickurl;
|
||||||
|
|
||||||
|
var callbackTimer = setInterval(function() {
|
||||||
|
let files = dictinput.files;
|
||||||
|
if(files.length>0){
|
||||||
|
if(!btnLookup.disabled){
|
||||||
|
clearInterval(callbackTimer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}else
|
||||||
|
location.href=clickurl;
|
||||||
|
dictinput.dispatchEvent(new Event('change', {'bubbles': true}));
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
135
en/searchurl/mdict/ripemd128.js
Normal file
135
en/searchurl/mdict/ripemd128.js
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* A pure JavaScript implementation of RIPEMD128 using Uint8Array as input/output.
|
||||||
|
* By Feng Dihai <fengdh@gmail.com>, 2015/07/09
|
||||||
|
*
|
||||||
|
* Based on coiscir/jsdigest (https://github.com/coiscir/jsdigest/blob/master/src/hash/ripemd128.js)
|
||||||
|
*
|
||||||
|
* ripemd128.js is free software released under terms of the MIT License.
|
||||||
|
* You can get a copy on http://opensource.org/licenses/MIT.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* RIPEMD-128 (c) 1996 Hans Dobbertin, Antoon Bosselaers, and Bart Preneel
|
||||||
|
*/
|
||||||
|
var ripemd128;
|
||||||
|
(function() {
|
||||||
|
// implementation
|
||||||
|
|
||||||
|
// convert array of number to Uint32Array
|
||||||
|
function asUint32Array(arr) {
|
||||||
|
return new Uint32Array(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// concat 2 typed array
|
||||||
|
function concat(a, b) {
|
||||||
|
var c = new a.constructor(a.length + b.length);
|
||||||
|
c.set(a);
|
||||||
|
c.set(b, a.length);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// swap high and low bits of a 32-bit int.
|
||||||
|
function rotl( x, n ) {
|
||||||
|
return ( x >>> ( 32 - n ) ) | ( x << n );
|
||||||
|
}
|
||||||
|
|
||||||
|
var DIGEST = 128,
|
||||||
|
BLOCK = 64,
|
||||||
|
S = [
|
||||||
|
[ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8 ], // round 1
|
||||||
|
[ 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12 ], // round 2
|
||||||
|
[ 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5 ], // round 3
|
||||||
|
[ 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12 ], // round 4
|
||||||
|
[ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6 ], // parallel round 1
|
||||||
|
[ 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11 ], // parallel round 2
|
||||||
|
[ 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5 ], // parallel round 3
|
||||||
|
[ 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8 ] // parallel round 4
|
||||||
|
].map(asUint32Array),
|
||||||
|
X = [
|
||||||
|
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ], // round 1
|
||||||
|
[ 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8 ], // round 2
|
||||||
|
[ 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12 ], // round 3
|
||||||
|
[ 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2 ], // round 4
|
||||||
|
[ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12 ], // parallel round 1
|
||||||
|
[ 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2 ], // parallel round 2
|
||||||
|
[ 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13 ], // parallel round 3
|
||||||
|
[ 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14 ] // parallel round 4
|
||||||
|
].map(asUint32Array),
|
||||||
|
K = asUint32Array([
|
||||||
|
0x00000000, // FF
|
||||||
|
0x5a827999, // GG
|
||||||
|
0x6ed9eba1, // HH
|
||||||
|
0x8f1bbcdc, // II
|
||||||
|
0x50a28be6, // III
|
||||||
|
0x5c4dd124, // HHH
|
||||||
|
0x6d703ef3, // GGG
|
||||||
|
0x00000000 // FFF
|
||||||
|
]),
|
||||||
|
F = [
|
||||||
|
function ( x, y, z ) {
|
||||||
|
return ( x ^ y ^ z );
|
||||||
|
},
|
||||||
|
function ( x, y, z ) {
|
||||||
|
return ( x & y ) | ( ( ~x ) & z );
|
||||||
|
},
|
||||||
|
function ( x, y, z ) {
|
||||||
|
return ( x | ( ~y ) ) ^ z;
|
||||||
|
},
|
||||||
|
function ( x, y, z ) {
|
||||||
|
return ( x & z ) | ( y & ( ~z ) );
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
ripemd128 = function( data ) {
|
||||||
|
var aa, bb, cc, dd, aaa, bbb, ccc, ddd, i, l, r, rr, t, tmp, x,
|
||||||
|
hash = new Uint32Array([ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476 ]),
|
||||||
|
bytes = data.length;
|
||||||
|
|
||||||
|
var padding = new Uint8Array(( ( bytes % 64 ) < 56 ? 56 : 120 ) - ( bytes % 64 ));
|
||||||
|
padding[0] = [0x80];
|
||||||
|
|
||||||
|
data = new Uint32Array(concat(data, padding).buffer);
|
||||||
|
|
||||||
|
// ending with check bits (= little endian 64-bit int, 8 * data.length)
|
||||||
|
bytes = bytes << 3;
|
||||||
|
x = concat( data, [bytes, bytes >> 31 >> 1]);
|
||||||
|
// update hash
|
||||||
|
for ( i = 0, t = 0, l = x.length; i < l; i += 16, t = 0 ) {
|
||||||
|
aa = aaa = hash[0];
|
||||||
|
bb = bbb = hash[1];
|
||||||
|
cc = ccc = hash[2];
|
||||||
|
dd = ddd = hash[3];
|
||||||
|
|
||||||
|
for ( ; t < 64; ++t ) {
|
||||||
|
r = ~~( t / 16 );
|
||||||
|
aa = rotl(aa + F[r]( bb, cc, dd ) + x[ i + X[r][ t % 16 ] ] + K[r], S[r][ t % 16 ]);
|
||||||
|
|
||||||
|
tmp = dd;
|
||||||
|
dd = cc;
|
||||||
|
cc = bb;
|
||||||
|
bb = aa;
|
||||||
|
aa = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ( ; t < 128; ++t ) {
|
||||||
|
r = ~~( t / 16 );
|
||||||
|
rr = ~~( ( 63 - ( t % 64 ) ) / 16 );
|
||||||
|
aaa = rotl(aaa + F[rr]( bbb, ccc, ddd ) + x[ i + X[r][ t % 16 ] ] + K[r], S[r][ t % 16 ]);
|
||||||
|
|
||||||
|
tmp = ddd;
|
||||||
|
ddd = ccc;
|
||||||
|
ccc = bbb;
|
||||||
|
bbb = aaa;
|
||||||
|
aaa = tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
ddd = hash[1] + cc + ddd;
|
||||||
|
hash[1] = hash[2] + dd + aaa;
|
||||||
|
hash[2] = hash[3] + aa + bbb;
|
||||||
|
hash[3] = hash[0] + bb + ccc;
|
||||||
|
hash[0] = ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array( hash.buffer );
|
||||||
|
}
|
||||||
|
|
||||||
|
}());
|
387
en/searchurl/mdict/selectize.default.css
Normal file
387
en/searchurl/mdict/selectize.default.css
Normal file
|
@ -0,0 +1,387 @@
|
||||||
|
/**
|
||||||
|
* selectize.default.css (v0.12.1) - Default Theme
|
||||||
|
* Copyright (c) 2013–2015 Brian Reavis & contributors
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @author Brian Reavis <brian@thirdroute.com>
|
||||||
|
*/
|
||||||
|
.selectize-control.plugin-drag_drop.multi > .selectize-input > div.ui-sortable-placeholder {
|
||||||
|
visibility: visible !important;
|
||||||
|
background: #f2f2f2 !important;
|
||||||
|
background: rgba(0, 0, 0, 0.06) !important;
|
||||||
|
border: 0 none !important;
|
||||||
|
-webkit-box-shadow: inset 0 0 12px 4px #ffffff;
|
||||||
|
box-shadow: inset 0 0 12px 4px #ffffff;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-drag_drop .ui-sortable-placeholder::after {
|
||||||
|
content: '!';
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-drag_drop .ui-sortable-helper {
|
||||||
|
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.selectize-dropdown-header {
|
||||||
|
position: relative;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-bottom: 1px solid #d0d0d0;
|
||||||
|
background: #f8f8f8;
|
||||||
|
-webkit-border-radius: 3px 3px 0 0;
|
||||||
|
-moz-border-radius: 3px 3px 0 0;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
.selectize-dropdown-header-close {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
color: #303030;
|
||||||
|
opacity: 0.4;
|
||||||
|
margin-top: -12px;
|
||||||
|
line-height: 20px;
|
||||||
|
font-size: 20px !important;
|
||||||
|
}
|
||||||
|
.selectize-dropdown-header-close:hover {
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
.selectize-dropdown.plugin-optgroup_columns .optgroup {
|
||||||
|
border-right: 1px solid #f2f2f2;
|
||||||
|
border-top: 0 none;
|
||||||
|
float: left;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.selectize-dropdown.plugin-optgroup_columns .optgroup:last-child {
|
||||||
|
border-right: 0 none;
|
||||||
|
}
|
||||||
|
.selectize-dropdown.plugin-optgroup_columns .optgroup:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.selectize-dropdown.plugin-optgroup_columns .optgroup-header {
|
||||||
|
border-top: 0 none;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-remove_button [data-value] {
|
||||||
|
position: relative;
|
||||||
|
padding-right: 24px !important;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-remove_button [data-value] .remove {
|
||||||
|
z-index: 1;
|
||||||
|
/* fixes ie bug (see #392) */
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 17px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: middle;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 0 0 0;
|
||||||
|
border-left: 1px solid #0073bb;
|
||||||
|
-webkit-border-radius: 0 2px 2px 0;
|
||||||
|
-moz-border-radius: 0 2px 2px 0;
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-remove_button [data-value] .remove:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-remove_button [data-value].active .remove {
|
||||||
|
border-left-color: #00578d;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-remove_button .disabled [data-value] .remove:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.selectize-control.plugin-remove_button .disabled [data-value] .remove {
|
||||||
|
border-left-color: #aaaaaa;
|
||||||
|
}
|
||||||
|
.selectize-control {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.selectize-dropdown,
|
||||||
|
.selectize-input,
|
||||||
|
.selectize-input input {
|
||||||
|
color: #303030;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 18px;
|
||||||
|
-webkit-font-smoothing: inherit;
|
||||||
|
}
|
||||||
|
.selectize-input,
|
||||||
|
.selectize-control.single .selectize-input.input-active {
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: text;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.selectize-input {
|
||||||
|
border: 1px solid #d0d0d0;
|
||||||
|
padding: 8px 8px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input.has-items {
|
||||||
|
padding: 5px 8px 2px;
|
||||||
|
}
|
||||||
|
.selectize-input.full {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
.selectize-input.disabled,
|
||||||
|
.selectize-input.disabled * {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
.selectize-input.focus {
|
||||||
|
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
.selectize-input.dropdown-active {
|
||||||
|
-webkit-border-radius: 3px 3px 0 0;
|
||||||
|
-moz-border-radius: 3px 3px 0 0;
|
||||||
|
border-radius: 3px 3px 0 0;
|
||||||
|
}
|
||||||
|
.selectize-input > * {
|
||||||
|
vertical-align: baseline;
|
||||||
|
display: -moz-inline-stack;
|
||||||
|
display: inline-block;
|
||||||
|
zoom: 1;
|
||||||
|
*display: inline;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input > div {
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 0 3px 3px 0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
background: #1da7ee;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #0073bb;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input > div.active {
|
||||||
|
background: #92c836;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #00578d;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input.disabled > div,
|
||||||
|
.selectize-control.multi .selectize-input.disabled > div.active {
|
||||||
|
color: #ffffff;
|
||||||
|
background: #d2d2d2;
|
||||||
|
border: 1px solid #aaaaaa;
|
||||||
|
}
|
||||||
|
.selectize-input > input {
|
||||||
|
display: inline-block !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-height: none !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
margin: 0 1px !important;
|
||||||
|
text-indent: 0 !important;
|
||||||
|
border: 0 none !important;
|
||||||
|
background: none !important;
|
||||||
|
line-height: inherit !important;
|
||||||
|
-webkit-user-select: auto !important;
|
||||||
|
-webkit-box-shadow: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
.selectize-input > input::-ms-clear {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.selectize-input > input:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.selectize-input::after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
.selectize-input.dropdown-active::before {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
background: #f0f0f0;
|
||||||
|
height: 1px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.selectize-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
border: 1px solid #d0d0d0;
|
||||||
|
background: #ffffff;
|
||||||
|
margin: -1px 0 0 0;
|
||||||
|
border-top: 0 none;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
-webkit-border-radius: 0 0 3px 3px;
|
||||||
|
-moz-border-radius: 0 0 3px 3px;
|
||||||
|
border-radius: 0 0 3px 3px;
|
||||||
|
}
|
||||||
|
.selectize-dropdown [data-selectable] {
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.selectize-dropdown [data-selectable] .highlight {
|
||||||
|
background: rgba(125, 168, 208, 0.2);
|
||||||
|
-webkit-border-radius: 1px;
|
||||||
|
-moz-border-radius: 1px;
|
||||||
|
border-radius: 1px;
|
||||||
|
}
|
||||||
|
.selectize-dropdown [data-selectable],
|
||||||
|
.selectize-dropdown .optgroup-header {
|
||||||
|
padding: 5px 8px;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .optgroup:first-child .optgroup-header {
|
||||||
|
border-top: 0 none;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .optgroup-header {
|
||||||
|
color: #303030;
|
||||||
|
background: #ffffff;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .active {
|
||||||
|
background-color: #f5fafd;
|
||||||
|
color: #495c68;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .active.create {
|
||||||
|
color: #495c68;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .create {
|
||||||
|
color: rgba(48, 48, 48, 0.5);
|
||||||
|
}
|
||||||
|
.selectize-dropdown-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
.selectize-control.single .selectize-input,
|
||||||
|
.selectize-control.single .selectize-input input {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.selectize-control.single .selectize-input.input-active,
|
||||||
|
.selectize-control.single .selectize-input.input-active input {
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
.selectize-control.single .selectize-input:after {
|
||||||
|
content: ' ';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 15px;
|
||||||
|
margin-top: -3px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 5px 5px 0 5px;
|
||||||
|
border-color: #808080 transparent transparent transparent;
|
||||||
|
}
|
||||||
|
.selectize-control.single .selectize-input.dropdown-active:after {
|
||||||
|
margin-top: -4px;
|
||||||
|
border-width: 0 5px 5px 5px;
|
||||||
|
border-color: transparent transparent #808080 transparent;
|
||||||
|
}
|
||||||
|
.selectize-control.rtl.single .selectize-input:after {
|
||||||
|
left: 15px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
.selectize-control.rtl .selectize-input > input {
|
||||||
|
margin: 0 4px 0 -2px !important;
|
||||||
|
}
|
||||||
|
.selectize-control .selectize-input.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input.has-items {
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input.disabled [data-value] {
|
||||||
|
color: #999;
|
||||||
|
text-shadow: none;
|
||||||
|
background: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input.disabled [data-value],
|
||||||
|
.selectize-control.multi .selectize-input.disabled [data-value] .remove {
|
||||||
|
border-color: #e6e6e6;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input.disabled [data-value] .remove {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input [data-value] {
|
||||||
|
text-shadow: 0 1px 0 rgba(0, 51, 83, 0.3);
|
||||||
|
-webkit-border-radius: 3px;
|
||||||
|
-moz-border-radius: 3px;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #1b9dec;
|
||||||
|
background-image: -moz-linear-gradient(top, #1da7ee, #178ee9);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#1da7ee), to(#178ee9));
|
||||||
|
background-image: -webkit-linear-gradient(top, #1da7ee, #178ee9);
|
||||||
|
background-image: -o-linear-gradient(top, #1da7ee, #178ee9);
|
||||||
|
background-image: linear-gradient(to bottom, #1da7ee, #178ee9);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff1da7ee', endColorstr='#ff178ee9', GradientType=0);
|
||||||
|
-webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03);
|
||||||
|
box-shadow: 0 1px 0 rgba(0,0,0,0.2),inset 0 1px rgba(255,255,255,0.03);
|
||||||
|
}
|
||||||
|
.selectize-control.multi .selectize-input [data-value].active {
|
||||||
|
background-color: #0085d4;
|
||||||
|
background-image: -moz-linear-gradient(top, #008fd8, #0075cf);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#008fd8), to(#0075cf));
|
||||||
|
background-image: -webkit-linear-gradient(top, #008fd8, #0075cf);
|
||||||
|
background-image: -o-linear-gradient(top, #008fd8, #0075cf);
|
||||||
|
background-image: linear-gradient(to bottom, #008fd8, #0075cf);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff008fd8', endColorstr='#ff0075cf', GradientType=0);
|
||||||
|
}
|
||||||
|
.selectize-control.single .selectize-input {
|
||||||
|
-webkit-box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8);
|
||||||
|
box-shadow: 0 1px 0 rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.8);
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
background-image: -moz-linear-gradient(top, #fefefe, #f2f2f2);
|
||||||
|
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fefefe), to(#f2f2f2));
|
||||||
|
background-image: -webkit-linear-gradient(top, #fefefe, #f2f2f2);
|
||||||
|
background-image: -o-linear-gradient(top, #fefefe, #f2f2f2);
|
||||||
|
background-image: linear-gradient(to bottom, #fefefe, #f2f2f2);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffefefe', endColorstr='#fff2f2f2', GradientType=0);
|
||||||
|
}
|
||||||
|
.selectize-control.single .selectize-input,
|
||||||
|
.selectize-dropdown.single {
|
||||||
|
border-color: #b8b8b8;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .optgroup-header {
|
||||||
|
padding-top: 7px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .optgroup {
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.selectize-dropdown .optgroup:first-child {
|
||||||
|
border-top: 0 none;
|
||||||
|
}
|
11
index.html
11
index.html
|
@ -1 +1,10 @@
|
||||||
<meta http-equiv="refresh" content="0; url=en/index.html">
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>en/</title>
|
||||||
|
<link rel="canonical" href="en/">
|
||||||
|
<meta name="robots" content="noindex">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="refresh" content="0; url=en/">
|
||||||
|
</head>
|
||||||
|
</html>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta name="generator" content="Hugo 0.104.2" />
|
<meta name="generator" content="Hugo 0.104.3" />
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
|
|
@ -22,31 +22,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#dict-title {
|
#dict-title {
|
||||||
position: fixed;
|
display:none;
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
|
||||||
max-width: 300px;
|
|
||||||
font-size: 10px;
|
|
||||||
opacity: 0.9;
|
|
||||||
background: #DDD;
|
|
||||||
box-shadow: -2px -2px 4px 4px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#dict-title * {
|
|
||||||
font-size: 10px!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mdict-online-viewer {
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: "Georgia", "Times New Roman";
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mdict-online-viewer #definition {
|
|
||||||
font-size: 14px;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#word + .selectize-control {
|
#word + .selectize-control {
|
||||||
|
@ -59,9 +35,6 @@
|
||||||
<script src="require.js" data-main="mdict"></script>
|
<script src="require.js" data-main="mdict"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<section class="main-content">
|
|
||||||
|
|
||||||
<div id="mdict-online-viewer">
|
|
||||||
Choose a dictionary file (*.mdx + optional *.mdd): <input id="dictfile" type="file" multiple>
|
Choose a dictionary file (*.mdx + optional *.mdd): <input id="dictfile" type="file" multiple>
|
||||||
<p>
|
<p>
|
||||||
<input id="word" type="text" value="">
|
<input id="word" type="text" value="">
|
||||||
|
@ -70,9 +43,6 @@
|
||||||
<div id="dict-title"></div>
|
<div id="dict-title"></div>
|
||||||
<div id="definition">
|
<div id="definition">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<script>
|
<script>
|
||||||
//?file=.../xx.xx,.../xx.xxx#word=
|
//?file=.../xx.xx,.../xx.xxx#word=
|
||||||
var dictinput = document.getElementById('dictfile');
|
var dictinput = document.getElementById('dictfile');
|
||||||
|
@ -97,7 +67,6 @@
|
||||||
let filenames = location.search.substring(6);
|
let filenames = location.search.substring(6);
|
||||||
window.onload = function(){
|
window.onload = function(){
|
||||||
if(filenames.length>0){
|
if(filenames.length>0){
|
||||||
filenames = decodeURIComponent(filenames);
|
|
||||||
let clickurl = "i:5fdictinput.click():"+filenames;
|
let clickurl = "i:5fdictinput.click():"+filenames;
|
||||||
location.href=clickurl;
|
location.href=clickurl;
|
||||||
//setTimeout(()=>{dictinput.dispatchEvent(new Event('change', {'bubbles': true}));},100);
|
//setTimeout(()=>{dictinput.dispatchEvent(new Event('change', {'bubbles': true}));},100);
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
var file;
|
var file;
|
||||||
var blocksize = 1024;
|
var blocksize = 20480;
|
||||||
function loadSlice(){
|
function loadSlice(){
|
||||||
function render(u8array){
|
function render(u8array){
|
||||||
document.body.innerHTML = marked(new TextDecoder().decode(u8array));
|
document.body.innerHTML = marked(new TextDecoder().decode(u8array));
|
||||||
|
|
Loading…
Reference in a new issue