.'
+ );
+ }
+ }
+ addAttr(el, name, JSON.stringify(value));
+ // #6887 firefox doesn't update muted state if set via attribute
+ // even immediately after element creation
+ if (!el.component &&
+ name === 'muted' &&
+ platformMustUseProp(el.tag, el.attrsMap.type, name)) {
+ addProp(el, name, 'true');
+ }
+ }
+ }
+}
+
+function checkInFor (el) {
+ var parent = el;
+ while (parent) {
+ if (parent.for !== undefined) {
+ return true
+ }
+ parent = parent.parent;
+ }
+ return false
+}
+
+function parseModifiers (name) {
+ var match = name.match(modifierRE);
+ if (match) {
+ var ret = {};
+ match.forEach(function (m) { ret[m.slice(1)] = true; });
+ return ret
+ }
+}
+
+function makeAttrsMap (attrs) {
+ var map = {};
+ for (var i = 0, l = attrs.length; i < l; i++) {
+ if (
+ "development" !== 'production' &&
+ map[attrs[i].name] && !isIE && !isEdge
+ ) {
+ warn$2('duplicate attribute: ' + attrs[i].name);
+ }
+ map[attrs[i].name] = attrs[i].value;
+ }
+ return map
+}
+
+// for script (e.g. type="x/template") or style, do not decode content
+function isTextTag (el) {
+ return el.tag === 'script' || el.tag === 'style'
+}
+
+function isForbiddenTag (el) {
+ return (
+ el.tag === 'style' ||
+ (el.tag === 'script' && (
+ !el.attrsMap.type ||
+ el.attrsMap.type === 'text/javascript'
+ ))
+ )
+}
+
+var ieNSBug = /^xmlns:NS\d+/;
+var ieNSPrefix = /^NS\d+:/;
+
+/* istanbul ignore next */
+function guardIESVGBug (attrs) {
+ var res = [];
+ for (var i = 0; i < attrs.length; i++) {
+ var attr = attrs[i];
+ if (!ieNSBug.test(attr.name)) {
+ attr.name = attr.name.replace(ieNSPrefix, '');
+ res.push(attr);
+ }
+ }
+ return res
+}
+
+function checkForAliasModel (el, value) {
+ var _el = el;
+ while (_el) {
+ if (_el.for && _el.alias === value) {
+ warn$2(
+ "<" + (el.tag) + " v-model=\"" + value + "\">: " +
+ "You are binding v-model directly to a v-for iteration alias. " +
+ "This will not be able to modify the v-for source array because " +
+ "writing to the alias is like modifying a function local variable. " +
+ "Consider using an array of objects and use v-model on an object property instead."
+ );
+ }
+ _el = _el.parent;
+ }
+}
+
+/* */
+
+/**
+ * Expand input[v-model] with dyanmic type bindings into v-if-else chains
+ * Turn this:
+ *
+ * into this:
+ *
+ *
+ *
+ */
+
+function preTransformNode (el, options) {
+ if (el.tag === 'input') {
+ var map = el.attrsMap;
+ if (!map['v-model']) {
+ return
+ }
+
+ var typeBinding;
+ if (map[':type'] || map['v-bind:type']) {
+ typeBinding = getBindingAttr(el, 'type');
+ }
+ if (!map.type && !typeBinding && map['v-bind']) {
+ typeBinding = "(" + (map['v-bind']) + ").type";
+ }
+
+ if (typeBinding) {
+ var ifCondition = getAndRemoveAttr(el, 'v-if', true);
+ var ifConditionExtra = ifCondition ? ("&&(" + ifCondition + ")") : "";
+ var hasElse = getAndRemoveAttr(el, 'v-else', true) != null;
+ var elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true);
+ // 1. checkbox
+ var branch0 = cloneASTElement(el);
+ // process for on the main node
+ processFor(branch0);
+ addRawAttr(branch0, 'type', 'checkbox');
+ processElement(branch0, options);
+ branch0.processed = true; // prevent it from double-processed
+ branch0.if = "(" + typeBinding + ")==='checkbox'" + ifConditionExtra;
+ addIfCondition(branch0, {
+ exp: branch0.if,
+ block: branch0
+ });
+ // 2. add radio else-if condition
+ var branch1 = cloneASTElement(el);
+ getAndRemoveAttr(branch1, 'v-for', true);
+ addRawAttr(branch1, 'type', 'radio');
+ processElement(branch1, options);
+ addIfCondition(branch0, {
+ exp: "(" + typeBinding + ")==='radio'" + ifConditionExtra,
+ block: branch1
+ });
+ // 3. other
+ var branch2 = cloneASTElement(el);
+ getAndRemoveAttr(branch2, 'v-for', true);
+ addRawAttr(branch2, ':type', typeBinding);
+ processElement(branch2, options);
+ addIfCondition(branch0, {
+ exp: ifCondition,
+ block: branch2
+ });
+
+ if (hasElse) {
+ branch0.else = true;
+ } else if (elseIfCondition) {
+ branch0.elseif = elseIfCondition;
+ }
+
+ return branch0
+ }
+ }
+}
+
+function cloneASTElement (el) {
+ return createASTElement(el.tag, el.attrsList.slice(), el.parent)
+}
+
+var model$2 = {
+ preTransformNode: preTransformNode
+}
+
+var modules$1 = [
+ klass$1,
+ style$1,
+ model$2
+]
+
+/* */
+
+function text (el, dir) {
+ if (dir.value) {
+ addProp(el, 'textContent', ("_s(" + (dir.value) + ")"));
+ }
+}
+
+/* */
+
+function html (el, dir) {
+ if (dir.value) {
+ addProp(el, 'innerHTML', ("_s(" + (dir.value) + ")"));
+ }
+}
+
+var directives$1 = {
+ model: model,
+ text: text,
+ html: html
+}
+
+/* */
+
+var baseOptions = {
+ expectHTML: true,
+ modules: modules$1,
+ directives: directives$1,
+ isPreTag: isPreTag,
+ isUnaryTag: isUnaryTag,
+ mustUseProp: mustUseProp,
+ canBeLeftOpenTag: canBeLeftOpenTag,
+ isReservedTag: isReservedTag,
+ getTagNamespace: getTagNamespace,
+ staticKeys: genStaticKeys(modules$1)
+};
+
+/* */
+
+var isStaticKey;
+var isPlatformReservedTag;
+
+var genStaticKeysCached = cached(genStaticKeys$1);
+
+/**
+ * Goal of the optimizer: walk the generated template AST tree
+ * and detect sub-trees that are purely static, i.e. parts of
+ * the DOM that never needs to change.
+ *
+ * Once we detect these sub-trees, we can:
+ *
+ * 1. Hoist them into constants, so that we no longer need to
+ * create fresh nodes for them on each re-render;
+ * 2. Completely skip them in the patching process.
+ */
+function optimize (root, options) {
+ if (!root) { return }
+ isStaticKey = genStaticKeysCached(options.staticKeys || '');
+ isPlatformReservedTag = options.isReservedTag || no;
+ // first pass: mark all non-static nodes.
+ markStatic$1(root);
+ // second pass: mark static roots.
+ markStaticRoots(root, false);
+}
+
+function genStaticKeys$1 (keys) {
+ return makeMap(
+ 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
+ (keys ? ',' + keys : '')
+ )
+}
+
+function markStatic$1 (node) {
+ node.static = isStatic(node);
+ if (node.type === 1) {
+ // do not make component slot content static. this avoids
+ // 1. components not able to mutate slot nodes
+ // 2. static slot content fails for hot-reloading
+ if (
+ !isPlatformReservedTag(node.tag) &&
+ node.tag !== 'slot' &&
+ node.attrsMap['inline-template'] == null
+ ) {
+ return
+ }
+ for (var i = 0, l = node.children.length; i < l; i++) {
+ var child = node.children[i];
+ markStatic$1(child);
+ if (!child.static) {
+ node.static = false;
+ }
+ }
+ if (node.ifConditions) {
+ for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
+ var block = node.ifConditions[i$1].block;
+ markStatic$1(block);
+ if (!block.static) {
+ node.static = false;
+ }
+ }
+ }
+ }
+}
+
+function markStaticRoots (node, isInFor) {
+ if (node.type === 1) {
+ if (node.static || node.once) {
+ node.staticInFor = isInFor;
+ }
+ // For a node to qualify as a static root, it should have children that
+ // are not just static text. Otherwise the cost of hoisting out will
+ // outweigh the benefits and it's better off to just always render it fresh.
+ if (node.static && node.children.length && !(
+ node.children.length === 1 &&
+ node.children[0].type === 3
+ )) {
+ node.staticRoot = true;
+ return
+ } else {
+ node.staticRoot = false;
+ }
+ if (node.children) {
+ for (var i = 0, l = node.children.length; i < l; i++) {
+ markStaticRoots(node.children[i], isInFor || !!node.for);
+ }
+ }
+ if (node.ifConditions) {
+ for (var i$1 = 1, l$1 = node.ifConditions.length; i$1 < l$1; i$1++) {
+ markStaticRoots(node.ifConditions[i$1].block, isInFor);
+ }
+ }
+ }
+}
+
+function isStatic (node) {
+ if (node.type === 2) { // expression
+ return false
+ }
+ if (node.type === 3) { // text
+ return true
+ }
+ return !!(node.pre || (
+ !node.hasBindings && // no dynamic bindings
+ !node.if && !node.for && // not v-if or v-for or v-else
+ !isBuiltInTag(node.tag) && // not a built-in
+ isPlatformReservedTag(node.tag) && // not a component
+ !isDirectChildOfTemplateFor(node) &&
+ Object.keys(node).every(isStaticKey)
+ ))
+}
+
+function isDirectChildOfTemplateFor (node) {
+ while (node.parent) {
+ node = node.parent;
+ if (node.tag !== 'template') {
+ return false
+ }
+ if (node.for) {
+ return true
+ }
+ }
+ return false
+}
+
+/* */
+
+var fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
+var simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/;
+
+// KeyboardEvent.keyCode aliases
+var keyCodes = {
+ esc: 27,
+ tab: 9,
+ enter: 13,
+ space: 32,
+ up: 38,
+ left: 37,
+ right: 39,
+ down: 40,
+ 'delete': [8, 46]
+};
+
+// KeyboardEvent.key aliases
+var keyNames = {
+ esc: 'Escape',
+ tab: 'Tab',
+ enter: 'Enter',
+ space: ' ',
+ // #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
+ up: ['Up', 'ArrowUp'],
+ left: ['Left', 'ArrowLeft'],
+ right: ['Right', 'ArrowRight'],
+ down: ['Down', 'ArrowDown'],
+ 'delete': ['Backspace', 'Delete']
+};
+
+// #4868: modifiers that prevent the execution of the listener
+// need to explicitly return null so that we can determine whether to remove
+// the listener for .once
+var genGuard = function (condition) { return ("if(" + condition + ")return null;"); };
+
+var modifierCode = {
+ stop: '$event.stopPropagation();',
+ prevent: '$event.preventDefault();',
+ self: genGuard("$event.target !== $event.currentTarget"),
+ ctrl: genGuard("!$event.ctrlKey"),
+ shift: genGuard("!$event.shiftKey"),
+ alt: genGuard("!$event.altKey"),
+ meta: genGuard("!$event.metaKey"),
+ left: genGuard("'button' in $event && $event.button !== 0"),
+ middle: genGuard("'button' in $event && $event.button !== 1"),
+ right: genGuard("'button' in $event && $event.button !== 2")
+};
+
+function genHandlers (
+ events,
+ isNative,
+ warn
+) {
+ var res = isNative ? 'nativeOn:{' : 'on:{';
+ for (var name in events) {
+ res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
+ }
+ return res.slice(0, -1) + '}'
+}
+
+function genHandler (
+ name,
+ handler
+) {
+ if (!handler) {
+ return 'function(){}'
+ }
+
+ if (Array.isArray(handler)) {
+ return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
+ }
+
+ var isMethodPath = simplePathRE.test(handler.value);
+ var isFunctionExpression = fnExpRE.test(handler.value);
+
+ if (!handler.modifiers) {
+ if (isMethodPath || isFunctionExpression) {
+ return handler.value
+ }
+ /* istanbul ignore if */
+ return ("function($event){" + (handler.value) + "}") // inline statement
+ } else {
+ var code = '';
+ var genModifierCode = '';
+ var keys = [];
+ for (var key in handler.modifiers) {
+ if (modifierCode[key]) {
+ genModifierCode += modifierCode[key];
+ // left/right
+ if (keyCodes[key]) {
+ keys.push(key);
+ }
+ } else if (key === 'exact') {
+ var modifiers = (handler.modifiers);
+ genModifierCode += genGuard(
+ ['ctrl', 'shift', 'alt', 'meta']
+ .filter(function (keyModifier) { return !modifiers[keyModifier]; })
+ .map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
+ .join('||')
+ );
+ } else {
+ keys.push(key);
+ }
+ }
+ if (keys.length) {
+ code += genKeyFilter(keys);
+ }
+ // Make sure modifiers like prevent and stop get executed after key filtering
+ if (genModifierCode) {
+ code += genModifierCode;
+ }
+ var handlerCode = isMethodPath
+ ? ("return " + (handler.value) + "($event)")
+ : isFunctionExpression
+ ? ("return (" + (handler.value) + ")($event)")
+ : handler.value;
+ /* istanbul ignore if */
+ return ("function($event){" + code + handlerCode + "}")
+ }
+}
+
+function genKeyFilter (keys) {
+ return ("if(!('button' in $event)&&" + (keys.map(genFilterCode).join('&&')) + ")return null;")
+}
+
+function genFilterCode (key) {
+ var keyVal = parseInt(key, 10);
+ if (keyVal) {
+ return ("$event.keyCode!==" + keyVal)
+ }
+ var keyCode = keyCodes[key];
+ var keyName = keyNames[key];
+ return (
+ "_k($event.keyCode," +
+ (JSON.stringify(key)) + "," +
+ (JSON.stringify(keyCode)) + "," +
+ "$event.key," +
+ "" + (JSON.stringify(keyName)) +
+ ")"
+ )
+}
+
+/* */
+
+function on (el, dir) {
+ if ("development" !== 'production' && dir.modifiers) {
+ warn("v-on without argument does not support modifiers.");
+ }
+ el.wrapListeners = function (code) { return ("_g(" + code + "," + (dir.value) + ")"); };
+}
+
+/* */
+
+function bind$1 (el, dir) {
+ el.wrapData = function (code) {
+ return ("_b(" + code + ",'" + (el.tag) + "'," + (dir.value) + "," + (dir.modifiers && dir.modifiers.prop ? 'true' : 'false') + (dir.modifiers && dir.modifiers.sync ? ',true' : '') + ")")
+ };
+}
+
+/* */
+
+var baseDirectives = {
+ on: on,
+ bind: bind$1,
+ cloak: noop
+}
+
+/* */
+
+var CodegenState = function CodegenState (options) {
+ this.options = options;
+ this.warn = options.warn || baseWarn;
+ this.transforms = pluckModuleFunction(options.modules, 'transformCode');
+ this.dataGenFns = pluckModuleFunction(options.modules, 'genData');
+ this.directives = extend(extend({}, baseDirectives), options.directives);
+ var isReservedTag = options.isReservedTag || no;
+ this.maybeComponent = function (el) { return !isReservedTag(el.tag); };
+ this.onceId = 0;
+ this.staticRenderFns = [];
+};
+
+
+
+function generate (
+ ast,
+ options
+) {
+ var state = new CodegenState(options);
+ var code = ast ? genElement(ast, state) : '_c("div")';
+ return {
+ render: ("with(this){return " + code + "}"),
+ staticRenderFns: state.staticRenderFns
+ }
+}
+
+function genElement (el, state) {
+ if (el.staticRoot && !el.staticProcessed) {
+ return genStatic(el, state)
+ } else if (el.once && !el.onceProcessed) {
+ return genOnce(el, state)
+ } else if (el.for && !el.forProcessed) {
+ return genFor(el, state)
+ } else if (el.if && !el.ifProcessed) {
+ return genIf(el, state)
+ } else if (el.tag === 'template' && !el.slotTarget) {
+ return genChildren(el, state) || 'void 0'
+ } else if (el.tag === 'slot') {
+ return genSlot(el, state)
+ } else {
+ // component or element
+ var code;
+ if (el.component) {
+ code = genComponent(el.component, el, state);
+ } else {
+ var data = el.plain ? undefined : genData$2(el, state);
+
+ var children = el.inlineTemplate ? null : genChildren(el, state, true);
+ code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
+ }
+ // module transforms
+ for (var i = 0; i < state.transforms.length; i++) {
+ code = state.transforms[i](el, code);
+ }
+ return code
+ }
+}
+
+// hoist static sub-trees out
+function genStatic (el, state) {
+ el.staticProcessed = true;
+ state.staticRenderFns.push(("with(this){return " + (genElement(el, state)) + "}"));
+ return ("_m(" + (state.staticRenderFns.length - 1) + (el.staticInFor ? ',true' : '') + ")")
+}
+
+// v-once
+function genOnce (el, state) {
+ el.onceProcessed = true;
+ if (el.if && !el.ifProcessed) {
+ return genIf(el, state)
+ } else if (el.staticInFor) {
+ var key = '';
+ var parent = el.parent;
+ while (parent) {
+ if (parent.for) {
+ key = parent.key;
+ break
+ }
+ parent = parent.parent;
+ }
+ if (!key) {
+ "development" !== 'production' && state.warn(
+ "v-once can only be used inside v-for that is keyed. "
+ );
+ return genElement(el, state)
+ }
+ return ("_o(" + (genElement(el, state)) + "," + (state.onceId++) + "," + key + ")")
+ } else {
+ return genStatic(el, state)
+ }
+}
+
+function genIf (
+ el,
+ state,
+ altGen,
+ altEmpty
+) {
+ el.ifProcessed = true; // avoid recursion
+ return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
+}
+
+function genIfConditions (
+ conditions,
+ state,
+ altGen,
+ altEmpty
+) {
+ if (!conditions.length) {
+ return altEmpty || '_e()'
+ }
+
+ var condition = conditions.shift();
+ if (condition.exp) {
+ return ("(" + (condition.exp) + ")?" + (genTernaryExp(condition.block)) + ":" + (genIfConditions(conditions, state, altGen, altEmpty)))
+ } else {
+ return ("" + (genTernaryExp(condition.block)))
+ }
+
+ // v-if with v-once should generate code like (a)?_m(0):_m(1)
+ function genTernaryExp (el) {
+ return altGen
+ ? altGen(el, state)
+ : el.once
+ ? genOnce(el, state)
+ : genElement(el, state)
+ }
+}
+
+function genFor (
+ el,
+ state,
+ altGen,
+ altHelper
+) {
+ var exp = el.for;
+ var alias = el.alias;
+ var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
+ var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
+
+ if ("development" !== 'production' &&
+ state.maybeComponent(el) &&
+ el.tag !== 'slot' &&
+ el.tag !== 'template' &&
+ !el.key
+ ) {
+ state.warn(
+ "<" + (el.tag) + " v-for=\"" + alias + " in " + exp + "\">: component lists rendered with " +
+ "v-for should have explicit keys. " +
+ "See https://vuejs.org/guide/list.html#key for more info.",
+ true /* tip */
+ );
+ }
+
+ el.forProcessed = true; // avoid recursion
+ return (altHelper || '_l') + "((" + exp + ")," +
+ "function(" + alias + iterator1 + iterator2 + "){" +
+ "return " + ((altGen || genElement)(el, state)) +
+ '})'
+}
+
+function genData$2 (el, state) {
+ var data = '{';
+
+ // directives first.
+ // directives may mutate the el's other properties before they are generated.
+ var dirs = genDirectives(el, state);
+ if (dirs) { data += dirs + ','; }
+
+ // key
+ if (el.key) {
+ data += "key:" + (el.key) + ",";
+ }
+ // ref
+ if (el.ref) {
+ data += "ref:" + (el.ref) + ",";
+ }
+ if (el.refInFor) {
+ data += "refInFor:true,";
+ }
+ // pre
+ if (el.pre) {
+ data += "pre:true,";
+ }
+ // record original tag name for components using "is" attribute
+ if (el.component) {
+ data += "tag:\"" + (el.tag) + "\",";
+ }
+ // module data generation functions
+ for (var i = 0; i < state.dataGenFns.length; i++) {
+ data += state.dataGenFns[i](el);
+ }
+ // attributes
+ if (el.attrs) {
+ data += "attrs:{" + (genProps(el.attrs)) + "},";
+ }
+ // DOM props
+ if (el.props) {
+ data += "domProps:{" + (genProps(el.props)) + "},";
+ }
+ // event handlers
+ if (el.events) {
+ data += (genHandlers(el.events, false, state.warn)) + ",";
+ }
+ if (el.nativeEvents) {
+ data += (genHandlers(el.nativeEvents, true, state.warn)) + ",";
+ }
+ // slot target
+ // only for non-scoped slots
+ if (el.slotTarget && !el.slotScope) {
+ data += "slot:" + (el.slotTarget) + ",";
+ }
+ // scoped slots
+ if (el.scopedSlots) {
+ data += (genScopedSlots(el.scopedSlots, state)) + ",";
+ }
+ // component v-model
+ if (el.model) {
+ data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
+ }
+ // inline-template
+ if (el.inlineTemplate) {
+ var inlineTemplate = genInlineTemplate(el, state);
+ if (inlineTemplate) {
+ data += inlineTemplate + ",";
+ }
+ }
+ data = data.replace(/,$/, '') + '}';
+ // v-bind data wrap
+ if (el.wrapData) {
+ data = el.wrapData(data);
+ }
+ // v-on data wrap
+ if (el.wrapListeners) {
+ data = el.wrapListeners(data);
+ }
+ return data
+}
+
+function genDirectives (el, state) {
+ var dirs = el.directives;
+ if (!dirs) { return }
+ var res = 'directives:[';
+ var hasRuntime = false;
+ var i, l, dir, needRuntime;
+ for (i = 0, l = dirs.length; i < l; i++) {
+ dir = dirs[i];
+ needRuntime = true;
+ var gen = state.directives[dir.name];
+ if (gen) {
+ // compile-time directive that manipulates AST.
+ // returns true if it also needs a runtime counterpart.
+ needRuntime = !!gen(el, dir, state.warn);
+ }
+ if (needRuntime) {
+ hasRuntime = true;
+ res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
+ }
+ }
+ if (hasRuntime) {
+ return res.slice(0, -1) + ']'
+ }
+}
+
+function genInlineTemplate (el, state) {
+ var ast = el.children[0];
+ if ("development" !== 'production' && (
+ el.children.length !== 1 || ast.type !== 1
+ )) {
+ state.warn('Inline-template components must have exactly one child element.');
+ }
+ if (ast.type === 1) {
+ var inlineRenderFns = generate(ast, state.options);
+ return ("inlineTemplate:{render:function(){" + (inlineRenderFns.render) + "},staticRenderFns:[" + (inlineRenderFns.staticRenderFns.map(function (code) { return ("function(){" + code + "}"); }).join(',')) + "]}")
+ }
+}
+
+function genScopedSlots (
+ slots,
+ state
+) {
+ return ("scopedSlots:_u([" + (Object.keys(slots).map(function (key) {
+ return genScopedSlot(key, slots[key], state)
+ }).join(',')) + "])")
+}
+
+function genScopedSlot (
+ key,
+ el,
+ state
+) {
+ if (el.for && !el.forProcessed) {
+ return genForScopedSlot(key, el, state)
+ }
+ var fn = "function(" + (String(el.slotScope)) + "){" +
+ "return " + (el.tag === 'template'
+ ? el.if
+ ? ((el.if) + "?" + (genChildren(el, state) || 'undefined') + ":undefined")
+ : genChildren(el, state) || 'undefined'
+ : genElement(el, state)) + "}";
+ return ("{key:" + key + ",fn:" + fn + "}")
+}
+
+function genForScopedSlot (
+ key,
+ el,
+ state
+) {
+ var exp = el.for;
+ var alias = el.alias;
+ var iterator1 = el.iterator1 ? ("," + (el.iterator1)) : '';
+ var iterator2 = el.iterator2 ? ("," + (el.iterator2)) : '';
+ el.forProcessed = true; // avoid recursion
+ return "_l((" + exp + ")," +
+ "function(" + alias + iterator1 + iterator2 + "){" +
+ "return " + (genScopedSlot(key, el, state)) +
+ '})'
+}
+
+function genChildren (
+ el,
+ state,
+ checkSkip,
+ altGenElement,
+ altGenNode
+) {
+ var children = el.children;
+ if (children.length) {
+ var el$1 = children[0];
+ // optimize single v-for
+ if (children.length === 1 &&
+ el$1.for &&
+ el$1.tag !== 'template' &&
+ el$1.tag !== 'slot'
+ ) {
+ return (altGenElement || genElement)(el$1, state)
+ }
+ var normalizationType = checkSkip
+ ? getNormalizationType(children, state.maybeComponent)
+ : 0;
+ var gen = altGenNode || genNode;
+ return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
+ }
+}
+
+// determine the normalization needed for the children array.
+// 0: no normalization needed
+// 1: simple normalization needed (possible 1-level deep nested array)
+// 2: full normalization needed
+function getNormalizationType (
+ children,
+ maybeComponent
+) {
+ var res = 0;
+ for (var i = 0; i < children.length; i++) {
+ var el = children[i];
+ if (el.type !== 1) {
+ continue
+ }
+ if (needsNormalization(el) ||
+ (el.ifConditions && el.ifConditions.some(function (c) { return needsNormalization(c.block); }))) {
+ res = 2;
+ break
+ }
+ if (maybeComponent(el) ||
+ (el.ifConditions && el.ifConditions.some(function (c) { return maybeComponent(c.block); }))) {
+ res = 1;
+ }
+ }
+ return res
+}
+
+function needsNormalization (el) {
+ return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
+}
+
+function genNode (node, state) {
+ if (node.type === 1) {
+ return genElement(node, state)
+ } if (node.type === 3 && node.isComment) {
+ return genComment(node)
+ } else {
+ return genText(node)
+ }
+}
+
+function genText (text) {
+ return ("_v(" + (text.type === 2
+ ? text.expression // no need for () because already wrapped in _s()
+ : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
+}
+
+function genComment (comment) {
+ return ("_e(" + (JSON.stringify(comment.text)) + ")")
+}
+
+function genSlot (el, state) {
+ var slotName = el.slotName || '"default"';
+ var children = genChildren(el, state);
+ var res = "_t(" + slotName + (children ? ("," + children) : '');
+ var attrs = el.attrs && ("{" + (el.attrs.map(function (a) { return ((camelize(a.name)) + ":" + (a.value)); }).join(',')) + "}");
+ var bind$$1 = el.attrsMap['v-bind'];
+ if ((attrs || bind$$1) && !children) {
+ res += ",null";
+ }
+ if (attrs) {
+ res += "," + attrs;
+ }
+ if (bind$$1) {
+ res += (attrs ? '' : ',null') + "," + bind$$1;
+ }
+ return res + ')'
+}
+
+// componentName is el.component, take it as argument to shun flow's pessimistic refinement
+function genComponent (
+ componentName,
+ el,
+ state
+) {
+ var children = el.inlineTemplate ? null : genChildren(el, state, true);
+ return ("_c(" + componentName + "," + (genData$2(el, state)) + (children ? ("," + children) : '') + ")")
+}
+
+function genProps (props) {
+ var res = '';
+ for (var i = 0; i < props.length; i++) {
+ var prop = props[i];
+ /* istanbul ignore if */
+ {
+ res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";
+ }
+ }
+ return res.slice(0, -1)
+}
+
+// #3895, #4268
+function transformSpecialNewlines (text) {
+ return text
+ .replace(/\u2028/g, '\\u2028')
+ .replace(/\u2029/g, '\\u2029')
+}
+
+/* */
+
+// these keywords should not appear inside expressions, but operators like
+// typeof, instanceof and in are allowed
+var prohibitedKeywordRE = new RegExp('\\b' + (
+ 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
+ 'super,throw,while,yield,delete,export,import,return,switch,default,' +
+ 'extends,finally,continue,debugger,function,arguments'
+).split(',').join('\\b|\\b') + '\\b');
+
+// these unary operators should not be used as property/method names
+var unaryOperatorsRE = new RegExp('\\b' + (
+ 'delete,typeof,void'
+).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)');
+
+// strip strings in expressions
+var stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
+
+// detect problematic expressions in a template
+function detectErrors (ast) {
+ var errors = [];
+ if (ast) {
+ checkNode(ast, errors);
+ }
+ return errors
+}
+
+function checkNode (node, errors) {
+ if (node.type === 1) {
+ for (var name in node.attrsMap) {
+ if (dirRE.test(name)) {
+ var value = node.attrsMap[name];
+ if (value) {
+ if (name === 'v-for') {
+ checkFor(node, ("v-for=\"" + value + "\""), errors);
+ } else if (onRE.test(name)) {
+ checkEvent(value, (name + "=\"" + value + "\""), errors);
+ } else {
+ checkExpression(value, (name + "=\"" + value + "\""), errors);
+ }
+ }
+ }
+ }
+ if (node.children) {
+ for (var i = 0; i < node.children.length; i++) {
+ checkNode(node.children[i], errors);
+ }
+ }
+ } else if (node.type === 2) {
+ checkExpression(node.expression, node.text, errors);
+ }
+}
+
+function checkEvent (exp, text, errors) {
+ var stipped = exp.replace(stripStringRE, '');
+ var keywordMatch = stipped.match(unaryOperatorsRE);
+ if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {
+ errors.push(
+ "avoid using JavaScript unary operator as property name: " +
+ "\"" + (keywordMatch[0]) + "\" in expression " + (text.trim())
+ );
+ }
+ checkExpression(exp, text, errors);
+}
+
+function checkFor (node, text, errors) {
+ checkExpression(node.for || '', text, errors);
+ checkIdentifier(node.alias, 'v-for alias', text, errors);
+ checkIdentifier(node.iterator1, 'v-for iterator', text, errors);
+ checkIdentifier(node.iterator2, 'v-for iterator', text, errors);
+}
+
+function checkIdentifier (
+ ident,
+ type,
+ text,
+ errors
+) {
+ if (typeof ident === 'string') {
+ try {
+ new Function(("var " + ident + "=_"));
+ } catch (e) {
+ errors.push(("invalid " + type + " \"" + ident + "\" in expression: " + (text.trim())));
+ }
+ }
+}
+
+function checkExpression (exp, text, errors) {
+ try {
+ new Function(("return " + exp));
+ } catch (e) {
+ var keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE);
+ if (keywordMatch) {
+ errors.push(
+ "avoid using JavaScript keyword as property name: " +
+ "\"" + (keywordMatch[0]) + "\"\n Raw expression: " + (text.trim())
+ );
+ } else {
+ errors.push(
+ "invalid expression: " + (e.message) + " in\n\n" +
+ " " + exp + "\n\n" +
+ " Raw expression: " + (text.trim()) + "\n"
+ );
+ }
+ }
+}
+
+/* */
+
+function createFunction (code, errors) {
+ try {
+ return new Function(code)
+ } catch (err) {
+ errors.push({ err: err, code: code });
+ return noop
+ }
+}
+
+function createCompileToFunctionFn (compile) {
+ var cache = Object.create(null);
+
+ return function compileToFunctions (
+ template,
+ options,
+ vm
+ ) {
+ options = extend({}, options);
+ var warn$$1 = options.warn || warn;
+ delete options.warn;
+
+ /* istanbul ignore if */
+ {
+ // detect possible CSP restriction
+ try {
+ new Function('return 1');
+ } catch (e) {
+ if (e.toString().match(/unsafe-eval|CSP/)) {
+ warn$$1(
+ 'It seems you are using the standalone build of Vue.js in an ' +
+ 'environment with Content Security Policy that prohibits unsafe-eval. ' +
+ 'The template compiler cannot work in this environment. Consider ' +
+ 'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
+ 'templates into render functions.'
+ );
+ }
+ }
+ }
+
+ // check cache
+ var key = options.delimiters
+ ? String(options.delimiters) + template
+ : template;
+ if (cache[key]) {
+ return cache[key]
+ }
+
+ // compile
+ var compiled = compile(template, options);
+
+ // check compilation errors/tips
+ {
+ if (compiled.errors && compiled.errors.length) {
+ warn$$1(
+ "Error compiling template:\n\n" + template + "\n\n" +
+ compiled.errors.map(function (e) { return ("- " + e); }).join('\n') + '\n',
+ vm
+ );
+ }
+ if (compiled.tips && compiled.tips.length) {
+ compiled.tips.forEach(function (msg) { return tip(msg, vm); });
+ }
+ }
+
+ // turn code into functions
+ var res = {};
+ var fnGenErrors = [];
+ res.render = createFunction(compiled.render, fnGenErrors);
+ res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
+ return createFunction(code, fnGenErrors)
+ });
+
+ // check function generation errors.
+ // this should only happen if there is a bug in the compiler itself.
+ // mostly for codegen development use
+ /* istanbul ignore if */
+ {
+ if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
+ warn$$1(
+ "Failed to generate render function:\n\n" +
+ fnGenErrors.map(function (ref) {
+ var err = ref.err;
+ var code = ref.code;
+
+ return ((err.toString()) + " in\n\n" + code + "\n");
+ }).join('\n'),
+ vm
+ );
+ }
+ }
+
+ return (cache[key] = res)
+ }
+}
+
+/* */
+
+function createCompilerCreator (baseCompile) {
+ return function createCompiler (baseOptions) {
+ function compile (
+ template,
+ options
+ ) {
+ var finalOptions = Object.create(baseOptions);
+ var errors = [];
+ var tips = [];
+ finalOptions.warn = function (msg, tip) {
+ (tip ? tips : errors).push(msg);
+ };
+
+ if (options) {
+ // merge custom modules
+ if (options.modules) {
+ finalOptions.modules =
+ (baseOptions.modules || []).concat(options.modules);
+ }
+ // merge custom directives
+ if (options.directives) {
+ finalOptions.directives = extend(
+ Object.create(baseOptions.directives || null),
+ options.directives
+ );
+ }
+ // copy other options
+ for (var key in options) {
+ if (key !== 'modules' && key !== 'directives') {
+ finalOptions[key] = options[key];
+ }
+ }
+ }
+
+ var compiled = baseCompile(template, finalOptions);
+ {
+ errors.push.apply(errors, detectErrors(compiled.ast));
+ }
+ compiled.errors = errors;
+ compiled.tips = tips;
+ return compiled
+ }
+
+ return {
+ compile: compile,
+ compileToFunctions: createCompileToFunctionFn(compile)
+ }
+ }
+}
+
+/* */
+
+// `createCompilerCreator` allows creating compilers that use alternative
+// parser/optimizer/codegen, e.g the SSR optimizing compiler.
+// Here we just export a default compiler using the default parts.
+var createCompiler = createCompilerCreator(function baseCompile (
+ template,
+ options
+) {
+ var ast = parse(template.trim(), options);
+ if (options.optimize !== false) {
+ optimize(ast, options);
+ }
+ var code = generate(ast, options);
+ return {
+ ast: ast,
+ render: code.render,
+ staticRenderFns: code.staticRenderFns
+ }
+});
+
+/* */
+
+var ref$1 = createCompiler(baseOptions);
+var compileToFunctions = ref$1.compileToFunctions;
+
+/* */
+
+// check whether current browser encodes a char inside attribute values
+var div;
+function getShouldDecode (href) {
+ div = div || document.createElement('div');
+ div.innerHTML = href ? "
" : "";
+ return div.innerHTML.indexOf('
') > 0
+}
+
+// #3663: IE encodes newlines inside attribute values while other browsers don't
+var shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false;
+// #6828: chrome encodes content in a[href]
+var shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false;
+
+/* */
+
+var idToTemplate = cached(function (id) {
+ var el = query(id);
+ return el && el.innerHTML
+});
+
+var mount = Vue.prototype.$mount;
+Vue.prototype.$mount = function (
+ el,
+ hydrating
+) {
+ el = el && query(el);
+
+ /* istanbul ignore if */
+ if (el === document.body || el === document.documentElement) {
+ "development" !== 'production' && warn(
+ "Do not mount Vue to or - mount to normal elements instead."
+ );
+ return this
+ }
+
+ var options = this.$options;
+ // resolve template/el and convert to render function
+ if (!options.render) {
+ var template = options.template;
+ if (template) {
+ if (typeof template === 'string') {
+ if (template.charAt(0) === '#') {
+ template = idToTemplate(template);
+ /* istanbul ignore if */
+ if ("development" !== 'production' && !template) {
+ warn(
+ ("Template element not found or is empty: " + (options.template)),
+ this
+ );
+ }
+ }
+ } else if (template.nodeType) {
+ template = template.innerHTML;
+ } else {
+ {
+ warn('invalid template option:' + template, this);
+ }
+ return this
+ }
+ } else if (el) {
+ template = getOuterHTML(el);
+ }
+ if (template) {
+ /* istanbul ignore if */
+ if ("development" !== 'production' && config.performance && mark) {
+ mark('compile');
+ }
+
+ var ref = compileToFunctions(template, {
+ shouldDecodeNewlines: shouldDecodeNewlines,
+ shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
+ delimiters: options.delimiters,
+ comments: options.comments
+ }, this);
+ var render = ref.render;
+ var staticRenderFns = ref.staticRenderFns;
+ options.render = render;
+ options.staticRenderFns = staticRenderFns;
+
+ /* istanbul ignore if */
+ if ("development" !== 'production' && config.performance && mark) {
+ mark('compile end');
+ measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
+ }
+ }
+ }
+ return mount.call(this, el, hydrating)
+};
+
+/**
+ * Get outerHTML of elements, taking care
+ * of SVG elements in IE as well.
+ */
+function getOuterHTML (el) {
+ if (el.outerHTML) {
+ return el.outerHTML
+ } else {
+ var container = document.createElement('div');
+ container.appendChild(el.cloneNode(true));
+ return container.innerHTML
+ }
+}
+
+Vue.compile = compileToFunctions;
+
+return Vue;
+
+})));
diff --git a/resources/kiwix.qrc b/resources/kiwix.qrc
index 68b5a44..1940dbd 100644
--- a/resources/kiwix.qrc
+++ b/resources/kiwix.qrc
@@ -61,7 +61,6 @@
fonts/SegoeUI/seguisb.ttf
fonts/SegoeUI/segoeui.ttf
texts/about.html
-
css/style.css
icons/search_backward.svg
icons/search_forward.svg
diff --git a/resources/style.qrc b/resources/style.qrc
new file mode 100644
index 0000000..b459a57
--- /dev/null
+++ b/resources/style.qrc
@@ -0,0 +1,5 @@
+
+
+ css/style.css
+
+
diff --git a/resources/texts/_contentManager.html b/resources/texts/_contentManager.html
new file mode 100644
index 0000000..29f7705
--- /dev/null
+++ b/resources/texts/_contentManager.html
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ book.title }}
+
+
+ {{ niceBytes(book.size) }}
+
+
+ {{ book.date }}
+
+
+ {{ book.tags }}
+
+
+
+
+ {{ niceBytes(downloads[book.id].completedLength) }} / {{ niceBytes(downloads[book.id].totalLength) }}
+
+
+
+
+
+
+
+ {{ book.description }}
+
+
+
+
+
+
diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp
new file mode 100644
index 0000000..e85702f
--- /dev/null
+++ b/src/contentmanager.cpp
@@ -0,0 +1,238 @@
+#include "contentmanager.h"
+
+#include "kiwixapp.h"
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, QObject *parent)
+ : QObject(parent),
+ mp_library(library),
+ mp_downloader(downloader)
+{
+ // mp_view will be passed to the tab who will take ownership,
+ // so, we don't need to delete it.
+ mp_view = new ContentManagerView();
+ mp_view->registerObject("contentManager", this);
+ mp_view->setHtml();
+ setCurrentLanguage(QLocale().name().split("_").at(0));
+ connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());});
+ connect(this, &ContentManager::remoteParamsChanged, this, &ContentManager::updateRemoteLibrary);
+}
+
+
+void ContentManager::setLocal(bool local) {
+ if (local == m_local) {
+ return;
+ }
+ m_local = local;
+ m_currentPage = 0;
+ emit(remoteParamsChanged());
+ emit(booksChanged());
+}
+
+#define ADD_V(KEY, METH) {if(key==KEY) values.append(QString::fromStdString((b.METH())));}
+QStringList ContentManager::getBookInfos(QString id, const QStringList &keys)
+{
+ QStringList values;
+ if (id.endsWith(".zim")) {
+ id.resize(id.size()-4);
+ }
+ kiwix::Book& b = [=]()->kiwix::Book& {
+ try {
+ return mp_library->getBookById(id);
+ } catch (...) {
+ return m_remoteLibrary.getBookById(id.toStdString());
+ }
+ }();
+
+ for(auto& key: keys){
+ ADD_V("id", getId);
+ ADD_V("path", getPath);
+ ADD_V("indexPath", getIndexPath);
+ ADD_V("title", getTitle);
+ ADD_V("description", getDescription);
+ ADD_V("language", getLanguage);
+ ADD_V("creator", getCreator);
+ ADD_V("publisher", getPublisher);
+ ADD_V("date", getDate);
+ ADD_V("url", getUrl);
+ ADD_V("name", getName);
+ ADD_V("tags", getTags);
+ ADD_V("origId", getOrigId);
+ ADD_V("faviconMimeType", getFaviconMimeType);
+ ADD_V("downloadId", getDownloadId);
+ if (key == "favicon") {
+ auto s = b.getFavicon();
+ values.append(QByteArray::fromStdString(s).toBase64());
+ }
+ if (key == "size") {
+ values.append(QString::number(b.getSize()));
+ }
+ if (key == "articleCount") {
+ values.append(QString::number(b.getArticleCount()));
+ }
+ if (key == "mediaCount") {
+ values.append(QString::number(b.getMediaCount()));
+ }
+ }
+ return values;
+}
+#undef ADD_V
+
+void ContentManager::openBook(const QString &id)
+{
+ QUrl url("zim://"+id+".zim/");
+ KiwixApp::instance()->openUrl(url, true);
+}
+
+#define ADD_V(KEY, METH) {if(key==KEY) {values.append(QString::fromStdString((d->METH()))); continue;}}
+QStringList ContentManager::updateDownloadInfos(QString id, const QStringList &keys)
+{
+ QStringList values;
+ if (id.endsWith(".zim")) {
+ id.resize(id.size()-4);
+ }
+ auto& b = mp_library->getBookById(id);
+ kiwix::Download* d;
+ try {
+ d = mp_downloader->getDownload(b.getDownloadId());
+ } catch(...) {
+ b.setDownloadId("");
+ mp_library->save();
+ emit(mp_library->booksChanged());
+ return values;
+ }
+
+ d->updateStatus(true);
+ if (d->getStatus() == kiwix::Download::K_COMPLETE) {
+ b.setPath(d->getPath());
+ b.setDownloadId("");
+ mp_library->save();
+ emit(mp_library->booksChanged());
+ }
+ for(auto& key: keys){
+ ADD_V("id", getDid);
+ if(key == "status") {
+ switch(d->getStatus()){
+ case kiwix::Download::K_ACTIVE:
+ values.append("active");
+ break;
+ case kiwix::Download::K_WAITING:
+ values.append("waiting");
+ break;
+ case kiwix::Download::K_PAUSED:
+ values.append("paused");
+ break;
+ case kiwix::Download::K_ERROR:
+ values.append("error");
+ break;
+ case kiwix::Download::K_COMPLETE:
+ values.append("completed");
+ break;
+ case kiwix::Download::K_REMOVED:
+ values.append("removed");
+ break;
+ default:
+ values.append("unknown");
+ }
+ continue;
+ }
+ ADD_V("followedBy", getFollowedBy);
+ ADD_V("path", getPath);
+ if(key == "totalLength") {
+ values.append(QString::number(d->getTotalLength()));
+ }
+ if(key == "completedLength") {
+ values.append(QString::number(d->getCompletedLength()));
+ }
+ if(key == "downloadSpeed") {
+ values.append(QString::number(d->getDownloadSpeed()));
+ }
+ if(key == "verifiedLength") {
+ values.append(QString::number(d->getVerifiedLength()));
+ }
+ }
+ return values;
+}
+#undef ADD_V
+
+QString ContentManager::downloadBook(const QString &id)
+{
+ auto& book = [&]()->kiwix::Book& {
+ try {
+ return m_remoteLibrary.getBookById(id.toStdString());
+ } catch (...) {
+ return mp_library->getBookById(id);
+ }
+ }();
+ auto download = mp_downloader->startDownload(book.getUrl());
+ book.setDownloadId(download->getDid());
+ mp_library->addBookToLibrary(book);
+ mp_library->save();
+ emit(mp_library->booksChanged());
+ emit(booksChanged());
+ return QString::fromStdString(download->getDid());
+}
+
+QStringList ContentManager::getDownloadIds()
+{
+ QStringList list;
+ for(auto& id: mp_downloader->getDownloadIds()) {
+ list.append(QString::fromStdString(id));
+ }
+ return list;
+}
+
+void ContentManager::setCurrentLanguage(QString language)
+{
+ if (language.length() == 2) {
+ try {
+ language = QString::fromStdString(
+ kiwix::converta2toa3(language.toStdString()));
+ } catch (std::out_of_range&) {}
+ }
+ m_currentLanguage = language;
+ emit(currentLangChanged());
+}
+
+#define CATALOG_HOST "http://library.kiwix.org"
+void ContentManager::updateRemoteLibrary() {
+ QUrlQuery query;
+ query.addQueryItem("lang", m_currentLanguage);
+ query.addQueryItem("count", QString::number(m_booksPerPage));
+ query.addQueryItem("start", QString::number(getStartBookIndex()));
+ QUrl url;
+ url.setScheme("http");
+ url.setHost("localhost");
+ url.setPort(8080);
+ url.setPath("/catalog/search.xml");
+ url.setQuery(query);
+ qInfo() << "Downloading" << url;
+ kiwix::Manager manager(&m_remoteLibrary);
+ try {
+ auto allContent = kiwix::download(url.toString().toStdString());
+ manager.readOpds(allContent, CATALOG_HOST);
+ } catch (runtime_error&) {}
+}
+
+QStringList ContentManager::getBookIds() {
+ if (m_local) {
+ return mp_library->getBookIds().mid(getStartBookIndex(), m_booksPerPage);
+ } else {
+ auto bookIds = m_remoteLibrary.getBooksIds();
+ QStringList list;
+ for(auto i=0; i
+#include
+#include "library.h"
+#include "contentmanagerview.h"
+#include
+
+class ContentManager : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(int booksPerPage MEMBER m_booksPerPage NOTIFY booksChanged)
+ Q_PROPERTY(int nbPages READ getNbPages NOTIFY booksChanged)
+ Q_PROPERTY(int currentPage MEMBER m_currentPage WRITE setCurrentPage NOTIFY booksChanged)
+ Q_PROPERTY(int startBookIndex READ getStartBookIndex NOTIFY booksChanged)
+ Q_PROPERTY(int endBookIndex READ getEndBookIndex NOTIFY booksChanged)
+ Q_PROPERTY(QStringList bookIds READ getBookIds NOTIFY booksChanged)
+ Q_PROPERTY(QStringList downloadIds READ getDownloadIds NOTIFY downloadsChanged)
+ Q_PROPERTY(QString currentLanguage MEMBER m_currentLanguage WRITE setCurrentLanguage NOTIFY currentLangChanged)
+
+public:
+
+ explicit ContentManager(Library* library, kiwix::Downloader *downloader, QObject *parent = nullptr);
+ virtual ~ContentManager() {}
+
+ ContentManagerView* getView() { return mp_view; }
+ void setLocal(bool local);
+ QStringList getDownloadIds();
+private:
+ Library* mp_library;
+ kiwix::Library m_remoteLibrary;
+ kiwix::Downloader* mp_downloader;
+ ContentManagerView* mp_view;
+ int m_booksPerPage = 10;
+ int m_currentPage = 0;
+ bool m_local = true;
+ QString m_currentLanguage;
+ void setCurrentPage(int currentPage) {
+ m_currentPage = max(0, min(currentPage, getNbPages()));
+ emit(booksChanged());
+ }
+ void setCurrentLanguage(QString language);
+
+ QStringList getBookIds();
+
+signals:
+ void booksChanged();
+ void downloadsChanged();
+ void currentLangChanged();
+
+public slots:
+ int getNbPages() {
+ if (m_local) {
+ return round(float(mp_library->getBookIds().length()) / m_booksPerPage);
+ } else {
+ return round(float(m_remoteLibrary.getBooksIds().size()) / m_booksPerPage);
+ }
+ }
+ int getStartBookIndex() {
+ return m_currentPage * m_booksPerPage;
+ }
+ int getEndBookIndex() {
+ return min((m_currentPage+1) * m_booksPerPage, mp_library->getBookIds().length());
+ }
+ QStringList getBookInfos(QString id, const QStringList &keys);
+ void openBook(const QString& id);
+ QStringList updateDownloadInfos(QString id, const QStringList& keys);
+ QString downloadBook(const QString& id);
+ void updateRemoteLibrary();
+};
+
+#endif // CONTENTMANAGER_H
diff --git a/src/contentmanagerside.cpp b/src/contentmanagerside.cpp
new file mode 100644
index 0000000..9807d0a
--- /dev/null
+++ b/src/contentmanagerside.cpp
@@ -0,0 +1,25 @@
+#include "contentmanagerside.h"
+#include "ui_contentmanagerside.h"
+
+ContentManagerSide::ContentManagerSide(QWidget *parent) :
+ QWidget(parent),
+ mp_ui(new Ui::contentmanagerside)
+{
+ mp_ui->setupUi(this);
+ connect(mp_ui->allFileButton, &QRadioButton::toggled,
+ this, [=](bool checked) { this->mp_contentManager->setLocal(!checked); });
+ connect(mp_ui->localFileButton, &QRadioButton::toggled,
+ this, [=](bool checked) { this->mp_contentManager->setLocal(checked); });
+ connect(mp_ui->allFileButton, &QRadioButton::toggled,
+ this, [=](bool checked) { mp_ui->allFileButton->setStyleSheet(
+ checked ? "*{font-weight: bold}" : "");});
+ connect(mp_ui->localFileButton, &QRadioButton::toggled,
+ this, [=](bool checked) { mp_ui->localFileButton->setStyleSheet(
+ checked ?"*{font-weight: bold}" : "");});
+ mp_ui->localFileButton->setStyleSheet("*{font-weight: bold}");
+}
+
+ContentManagerSide::~ContentManagerSide()
+{
+ delete mp_ui;
+}
diff --git a/src/contentmanagerside.h b/src/contentmanagerside.h
new file mode 100644
index 0000000..919846f
--- /dev/null
+++ b/src/contentmanagerside.h
@@ -0,0 +1,26 @@
+#ifndef CONTENTMANAGERSIDE_H
+#define CONTENTMANAGERSIDE_H
+
+#include
+#include "contentmanager.h"
+
+namespace Ui {
+class contentmanagerside;
+}
+
+class ContentManagerSide : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit ContentManagerSide(QWidget *parent = 0);
+ ~ContentManagerSide();
+
+ void setContentManager(ContentManager* contentManager) { mp_contentManager = contentManager; }
+
+private:
+ Ui::contentmanagerside *mp_ui;
+ ContentManager* mp_contentManager;
+};
+
+#endif // CONTENTMANAGERSIDE_H
diff --git a/src/contentmanagerside.ui b/src/contentmanagerside.ui
new file mode 100644
index 0000000..e9765d5
--- /dev/null
+++ b/src/contentmanagerside.ui
@@ -0,0 +1,123 @@
+
+
+ contentmanagerside
+
+
+
+ 0
+ 0
+ 197
+ 366
+
+
+
+ Form
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+
+
+ true
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ All Files
+
+
+
+ -
+
+
+ Local Files
+
+
+ true
+
+
+
+ -
+
+
+ false
+
+
+ Browse By Category
+
+
+
+ -
+
+
+ false
+
+
+ Language
+
+
+
+ -
+
+
+ false
+
+
+ Content Type
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp
new file mode 100644
index 0000000..eee8634
--- /dev/null
+++ b/src/contentmanagerview.cpp
@@ -0,0 +1,24 @@
+#include "contentmanagerview.h"
+#include
+
+ContentManagerView::ContentManagerView(QWidget *parent)
+ : QWebEngineView(parent)
+{
+ page()->setWebChannel(&m_webChannel);
+}
+
+
+void ContentManagerView::registerObject(const QString& id, QObject* object)
+{
+ m_webChannel.registerObject(id, object);
+}
+
+
+void ContentManagerView::setHtml()
+{
+ QFile contentFile(":texts/_contentManager.html");
+ contentFile.open(QIODevice::ReadOnly);
+ auto byteContent = contentFile.readAll();
+ contentFile.close();
+ QWebEngineView::setHtml(byteContent);
+}
diff --git a/src/contentmanagerview.h b/src/contentmanagerview.h
new file mode 100644
index 0000000..d2241e3
--- /dev/null
+++ b/src/contentmanagerview.h
@@ -0,0 +1,17 @@
+#ifndef CONTENTMANAGERVIEW_H
+#define CONTENTMANAGERVIEW_H
+
+#include
+#include
+
+class ContentManagerView : public QWebEngineView
+{
+public:
+ ContentManagerView(QWidget *parent = Q_NULLPTR);
+ void registerObject(const QString &id, QObject *object);
+ void setHtml();
+private:
+ QWebChannel m_webChannel;
+};
+
+#endif // CONTENTMANAGERVIEW_H
diff --git a/src/kiwixapp.cpp b/src/kiwixapp.cpp
index 3938ba8..4b9699b 100644
--- a/src/kiwixapp.cpp
+++ b/src/kiwixapp.cpp
@@ -12,7 +12,10 @@
#include
KiwixApp::KiwixApp(int& argc, char *argv[])
- : QApplication(argc, argv)
+ : QApplication(argc, argv),
+ m_library(),
+ m_downloader(),
+ m_manager(&m_library, &m_downloader)
{
m_qtTranslator.load(QLocale(), "qt", "_",
QLibraryInfo::location(QLibraryInfo::TranslationsPath));
@@ -68,7 +71,10 @@ KiwixApp::KiwixApp(int& argc, char *argv[])
createAction();
mp_mainWindow = new MainWindow;
- mp_tabWidget = mp_mainWindow->getTabWidget();
+ mp_tabWidget = mp_mainWindow->getTabBar();
+ mp_tabWidget->setContentManagerView(m_manager.getView());
+ mp_mainWindow->getSideContentManager()->setContentManager(&m_manager);
+ setSideBar(CONTENTMANAGER_BAR);
postInit();
mp_errorDialog = new QErrorMessage(mp_mainWindow);
@@ -77,6 +83,7 @@ KiwixApp::KiwixApp(int& argc, char *argv[])
KiwixApp::~KiwixApp()
{
+ m_downloader.close();
delete mp_errorDialog;
delete mp_mainWindow;
}
@@ -101,7 +108,7 @@ void KiwixApp::openZimFile(const QString &zimfile)
}
QString zimId;
try {
- zimId = m_library.openBook(_zimfile);
+ zimId = m_library.openBookFromPath(_zimfile);
} catch (const std::exception& e) {
showMessage("Cannot open " + _zimfile + ": \n" + e.what());
return;
@@ -130,10 +137,29 @@ void KiwixApp::printPage()
}
}
+void KiwixApp::openUrl(const QString &url, bool newTab) {
+ openUrl(QUrl(url), newTab);
+}
+
void KiwixApp::openUrl(const QUrl &url, bool newTab) {
mp_tabWidget->openUrl(url, newTab);
}
+void KiwixApp::setSideBar(KiwixApp::SideBarType type)
+{
+ auto sideDockWidget = mp_mainWindow->getSideDockWidget();
+ switch(type) {
+ case SEARCH_BAR:
+ case CONTENTMANAGER_BAR:
+ sideDockWidget->setCurrentIndex(type);
+ sideDockWidget->show();
+ break;
+ case NONE:
+ sideDockWidget->hide();
+ break;
+ }
+}
+
void KiwixApp::openRandomUrl(bool newTab)
{
auto zimId = mp_tabWidget->currentZimId();
@@ -220,6 +246,8 @@ void KiwixApp::createAction()
CREATE_ACTION(FindInPageAction, tr("Find in page"));
SET_SHORTCUT(FindInPageAction, QKeySequence::Find);
+ connect(mpa_actions[FindInPageAction], &QAction::triggered,
+ this, [=]() { setSideBar(SEARCH_BAR); });
CREATE_ACTION_ICON(ToggleFullscreenAction, "full-screen-enter", tr("Set fullScreen"));
SET_SHORTCUT(ToggleFullscreenAction, QKeySequence::FullScreen);
@@ -277,9 +305,5 @@ void KiwixApp::createAction()
}
void KiwixApp::postInit() {
- auto realToggleAction = mp_mainWindow->getSideDockWidget()->toggleViewAction();
- auto proxyToggleAction = mpa_actions[FindInPageAction];
- connect(proxyToggleAction, &QAction::triggered, realToggleAction, &QAction::trigger);
- connect(realToggleAction, &QAction::toggled, proxyToggleAction, &QAction::setChecked);
- realToggleAction->toggle();
+ emit(m_library.booksChanged());
}
diff --git a/src/kiwixapp.h b/src/kiwixapp.h
index b53c9cf..63ebc46 100644
--- a/src/kiwixapp.h
+++ b/src/kiwixapp.h
@@ -2,8 +2,10 @@
#define KIWIXAPP_H
#include "library.h"
+#include "contentmanager.h"
#include "mainwindow.h"
-#include "tabwidget.h"
+#include "kiwix/downloader.h"
+#include "tabbar.h"
#include "tocsidebar.h"
#include "urlschemehandler.h"
#include "requestinterceptor.h"
@@ -47,12 +49,16 @@ public:
ExitAction,
MAX_ACTION
};
+ enum SideBarType {
+ SEARCH_BAR,
+ CONTENTMANAGER_BAR,
+ NONE
+ };
KiwixApp(int& argc, char *argv[]);
virtual ~KiwixApp();
static KiwixApp* instance();
- void openUrl(const QUrl& url, bool newTab=true);
void openRandomUrl(bool newTab=true);
void showMessage(const QString& message);
@@ -61,11 +67,15 @@ public:
RequestInterceptor* getRequestInterceptor() { return &m_requestInterceptor; }
Library* getLibrary() { return &m_library; }
MainWindow* getMainWindow() { return mp_mainWindow; }
- TabWidget* getTabWidget() { return mp_tabWidget; }
+ kiwix::Downloader* getDownloader() { return &m_downloader; }
+ TabBar* getTabWidget() { return mp_tabWidget; }
QAction* getAction(Actions action);
public slots:
void openZimFile(const QString& zimfile="");
+ void openUrl(const QString& url, bool newTab=true);
+ void openUrl(const QUrl& url, bool newTab=true);
+ void setSideBar(SideBarType type);
void printPage();
protected:
@@ -75,8 +85,11 @@ protected:
private:
QTranslator m_qtTranslator, m_appTranslator;
Library m_library;
+ kiwix::Downloader m_downloader;
+ ContentManager m_manager;
MainWindow* mp_mainWindow;
- TabWidget* mp_tabWidget;
+ TabBar* mp_tabWidget;
+ QWidget* mp_currentSideBar;
QErrorMessage* mp_errorDialog;
UrlSchemeHandler m_schemeHandler;
diff --git a/src/library.cpp b/src/library.cpp
index 46157bd..ea47d5d 100644
--- a/src/library.cpp
+++ b/src/library.cpp
@@ -1,25 +1,65 @@
#include "library.h"
+
+#include
+
#include
+class LibraryManipulator: public kiwix::LibraryManipulator {
+ public:
+ LibraryManipulator(Library* p_library)
+ : mp_library(p_library) {}
+ bool addBookToLibrary(kiwix::Book book) {
+ auto ret = mp_library->m_library.addBook(book);
+ emit(mp_library->booksChanged());
+ return ret;
+ }
+ Library* mp_library;
+};
+
Library::Library()
{
-
+ auto manipulator = LibraryManipulator(this);
+ auto manager = kiwix::Manager(&manipulator);
+ qInfo() << QString::fromStdString(getDataDirectory());
+ manager.readFile(appendToDirectory(getDataDirectory(),"library.xml"), false);
+ qInfo() << getBookIds().length();
+ emit(booksChanged());
}
-QString Library::openBook(const QString &zimPath)
+Library::~Library()
+{
+ save();
+}
+
+QString Library::openBookFromPath(const QString &zimPath)
{
for(auto it=m_readersMap.begin();
it != m_readersMap.end();
it++)
{
- if(QString::fromStdString(it->second->getZimFilePath()) == zimPath)
- return it->first;
+ if(QString::fromStdString(it.value()->getZimFilePath()) == zimPath)
+ return it.key();
}
qInfo() << "Opening" << zimPath;
auto zimPath_ = zimPath.toStdString();
auto reader = std::shared_ptr(new kiwix::Reader(zimPath_));
- auto id = QString::fromStdString(reader->getId() + ".zim");
+ auto _id(reader->getId());
+ auto id = QString::fromStdString(_id + ".zim");
+ kiwix::Book b;
+ b.update(*reader);
+ m_library.addBook(b);
+ m_readersMap[id] = reader;
+ save();
+ emit(booksChanged());
+ return id;
+}
+
+QString Library::openBookById(const QString& _id)
+{
+ auto& b = m_library.getBookById(_id.toStdString());
+ auto reader = std::shared_ptr(new kiwix::Reader(b.getPath()));
+ auto id = _id + ".zim";
m_readersMap[id] = reader;
return id;
}
@@ -28,6 +68,40 @@ std::shared_ptr Library::getReader(const QString &zimId)
{
auto it = m_readersMap.find(zimId);
if (it != m_readersMap.end())
- return it->second;
+ return it.value();
+ // No reader, try to open the file
+ try {
+ QString _id = zimId;
+ if (_id.endsWith(".zim")) _id.resize(_id.size()-4);
+ openBookById(_id);
+ return m_readersMap.find(zimId).value();
+ } catch(...) {}
return nullptr;
}
+
+QStringList Library::getBookIds()
+{
+ QStringList list;
+ for(auto& id: m_library.getBooksIds()) {
+ list.append(QString::fromStdString(id));
+ }
+ return list;
+}
+
+void Library::addBookToLibrary(kiwix::Book &book)
+{
+ m_library.addBook(book);
+}
+
+void Library::save()
+{
+ m_library.writeToFile(appendToDirectory(getDataDirectory(),"library.xml"));
+}
+
+kiwix::Book &Library::getBookById(QString id)
+{
+ if (id.endsWith(".zim")) {
+ id.resize(id.size()-4);
+ }
+ return m_library.getBookById(id.toStdString());
+}
diff --git a/src/library.h b/src/library.h
index 1427053..61e67c6 100644
--- a/src/library.h
+++ b/src/library.h
@@ -1,18 +1,47 @@
#ifndef LIBRARY_H
#define LIBRARY_H
-#include
-#include