| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 | /* SPDX-License-Identifier: AGPL-3.0-or-later */module.exports = function (grunt) {  const eachAsync = require('each-async');  function file_exists (filepath) {    // filter function to exit grunt task with error if a (src) file not exists    if (!grunt.file.exists(filepath)) {      grunt.fail.fatal('Could not find: ' + filepath, 42);    } else {      return true;    }  }  grunt.initConfig({    _brand: '../../../../src/brand',    _templates: '../../../templates',    pkg: grunt.file.readJSON('package.json'),    watch: {      scripts: {        files: ['gruntfile.js', 'src/**'],        tasks: [          'eslint',          'copy',          'uglify',          'less',          'image',          'svg2png',          'svg2jinja'        ]      }    },    eslint: {      options: {        overrideConfigFile: '.eslintrc.json',        failOnError: true,        fix: grunt.option('fix')      },      target: [        'gruntfile.js',        'svg4web.svgo.js',        'src/js/main/*.js',        'src/js/head/*.js',        '../__common__/js/*.js'      ],    },    stylelint: {      options: {        formatter: 'unix',      },      src: [        'src/less/**/*.less',      ]    },    copy: {      js: {        expand: true,        cwd: './node_modules',        dest: './js/',        flatten: true,        filter: 'isFile',        timestamp: true,        src: [          './leaflet/dist/leaflet.js',        ]      },      css: {        expand: true,        cwd: './node_modules',        dest: './css/',        flatten: true,        filter: 'isFile',        timestamp: true,        src: [          './leaflet/dist/leaflet.css',        ]      },      leaflet_images: {        expand: true,        cwd: './node_modules',        dest: './css/images/',        flatten: true,        filter: 'isFile',        timestamp: true,        src: [          './leaflet/dist/images/*.png',        ]      },    },    uglify: {      options: {        output: {          comments: 'some'        },        ie8: false,        warnings: true,        compress: false,        mangle: true,        sourceMap: {          includeSources: true        }      },      dist: {        files: {          'js/searxng.head.min.js': ['src/js/head/*.js'],          'js/searxng.min.js': [            'src/js/main/*.js',            './node_modules/autocomplete-js/dist/autocomplete.js'          ]        }      }    },    less: {      production: {        options: {          paths: ["less"],          plugins: [            new (require('less-plugin-clean-css'))()          ],          sourceMap: true,          sourceMapURL: (name) => { const s = name.split('/'); return s[s.length - 1] + '.map'; },          outputSourceFiles: true,        },        files: [          {            src: ['src/less/style-ltr.less'],            dest: 'css/searxng.min.css',            nonull: true,            filter: file_exists,          },          {            src: ['src/less/style-rtl.less'],            dest: 'css/searxng-rtl.min.css',            nonull: true,            filter: file_exists,          },        ],      },    },    image: {      svg4web: {        options: {          svgo: ['--config', 'svg4web.svgo.js']        },        files: {          '<%= _templates %>/__common__/searxng-wordmark.min.svg': '<%= _brand %>/searxng-wordmark.svg',          'img/searxng.svg': '<%= _brand %>/searxng.svg',          'img/img_load_error.svg': '<%= _brand %>/img_load_error.svg'        }      },      favicon: {        options: {          svgo: ['--config', 'svg4favicon.svgo.js']        },        files: {          'img/favicon.svg': '<%= _brand %>/searxng-wordmark.svg'        }      },    },    svg2png: {      favicon: {        files: {          'img/favicon.png': '<%= _brand %>/searxng-wordmark.svg',          'img/searxng.png': '<%= _brand %>/searxng.svg',        }      }    },    svg2jinja: {      all: {        src: {          'warning': 'node_modules/ionicons/dist/svg/alert-outline.svg',          'close': 'node_modules/ionicons/dist/svg/close-outline.svg',          'chevron-up-outline': 'node_modules/ionicons/dist/svg/chevron-up-outline.svg',          'chevron-right': 'node_modules/ionicons/dist/svg/chevron-forward-outline.svg',          'chevron-left': 'node_modules/ionicons/dist/svg/chevron-back-outline.svg',          'menu-outline': 'node_modules/ionicons/dist/svg/settings-outline.svg',          'ellipsis-vertical-outline': 'node_modules/ionicons/dist/svg/ellipsis-vertical-outline.svg',          'magnet-outline': 'node_modules/ionicons/dist/svg/magnet-outline.svg',          'globe-outline': 'node_modules/ionicons/dist/svg/globe-outline.svg',          'search-outline': 'node_modules/ionicons/dist/svg/search-outline.svg',          'image-outline': 'node_modules/ionicons/dist/svg/image-outline.svg',          'play-outline': 'node_modules/ionicons/dist/svg/play-outline.svg',          'newspaper-outline': 'node_modules/ionicons/dist/svg/newspaper-outline.svg',          'location-outline': 'node_modules/ionicons/dist/svg/location-outline.svg',          'musical-notes-outline': 'node_modules/ionicons/dist/svg/musical-notes-outline.svg',          'layers-outline': 'node_modules/ionicons/dist/svg/layers-outline.svg',          'school-outline': 'node_modules/ionicons/dist/svg/school-outline.svg',          'file-tray-full-outline': 'node_modules/ionicons/dist/svg/file-tray-full-outline.svg',          'people-outline': 'node_modules/ionicons/dist/svg/people-outline.svg',        },        dest: '../../../templates/simple/icons.html',      },    },  });  grunt.registerMultiTask('svg2jinja', 'Create Jinja2 macro', function () {    const ejs = require('ejs'), svgo = require('svgo');    const icons = {}    for (const iconName in this.data.src) {      const svgFileName = this.data.src[iconName];      try {        const svgContent = grunt.file.read(svgFileName, { encoding: 'utf8' })        const svgoResult = svgo.optimize(svgContent, {          path: svgFileName,          multipass: true,          plugins: [            {              name: "removeTitle",            },            {              name: "removeXMLNS",            },            {              name: "addAttributesToSVGElement",              params: {                attributes: [                  { "aria-hidden": "true" }                ]              }            }          ]        });        icons[iconName] = svgoResult.data.replace("'", "\\'");      } catch (err) {        grunt.log.error(err);      }    }    const template = `{# this file was generated by searx/static/themes/simple/gruntfile.js #}{%- set icons = {<% for (const iconName in icons) { %>  '<%- iconName %>':'<%- icons[iconName] %>',<% } %>}-%}{% macro icon(action, alt) -%}  {{ icons[action] | replace("ionicon", "ion-icon") | safe }}{%- endmacro %}{% macro icon_small(action) -%}  {{ icons[action] | replace("ionicon", "ion-icon-small") | safe }}{%- endmacro %}{% macro icon_big(action, alt) -%}  {{ icons[action] | replace("ionicon", "ion-icon-big") | safe }}{%- endmacro %}`;    const result = ejs.render(template, { icons });    grunt.file.write(this.data.dest, result, { encoding: 'utf8' });    grunt.log.ok(this.data.dest + " created");  });  grunt.registerMultiTask('svg2png', 'Convert SVG to PNG', function () {    const sharp = require('sharp'), done = this.async();    eachAsync(this.files, async (file, _index, next) => {      try {        if (file.src.length != 1) {          next("this task supports only one source per destination");        }        const info = await sharp(file.src[0])          .png({            force: true,            compressionLevel: 9,            palette: true,          })          .toFile(file.dest);        grunt.log.ok(file.dest + ' created (' + info.size + ' bytes, ' + info.width + 'px * ' + info.height + 'px)');        next();      } catch (error) {        grunt.fatal(error);        next(error);      }    }, error => {      if (error) {        grunt.fatal(error);        done(error);      } else {        done();      }    });  });  grunt.loadNpmTasks('grunt-contrib-watch');  grunt.loadNpmTasks('grunt-contrib-copy');  grunt.loadNpmTasks('grunt-contrib-uglify');  grunt.loadNpmTasks('grunt-image');  grunt.loadNpmTasks('grunt-contrib-less');  grunt.loadNpmTasks('grunt-contrib-cssmin');  grunt.loadNpmTasks('grunt-stylelint');  grunt.loadNpmTasks('grunt-eslint');  grunt.registerTask('test', ['eslint']);  grunt.registerTask('default', [    'eslint',    'stylelint',    'copy',    'uglify',    'less',    'image',    'svg2png',    'svg2jinja',  ]);};
 |