Getting results for Multi-word Query and showing limited results in Knowledge Graph

In Susper knowledge graph,we were unable to get results for multi word query from Wikipedia API and also we were unable to decide how much information should be shown in knowledge graph which was retrieved from Wikipedia API.For example for a query donald trump we do not get information (https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=donald%20trump) . Also for searching for any query like india (https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=india) we get a lot of information. Earlier we used Angular slice pipe to display only 600 characters in knowledge graph (Infobox) but almost for all queries the sentences were terminated before it was completed. In this blog, I will describe how we solved both problems in knowledge graph.

Getting results for multi word query from Wikipedia API:

On searching a lot we found that the results were present on Wikipedia for multi word queries but these queries must have its starting letters as capital letters. For example we do not get results for queries such as donald trump

i.e https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=donald%20trump

but when we make a query for Donald Trump  

i.e

https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro=&explaintext=&titles=Donald%20Trump  

But since in most of the search queries users use small letters, we need to convert the query such that each word starts with a capital letter. Since the Knowledge Graph has been implemented using ngrx pattern, this logic was easily implemented in knowledge effects. For this we will be selecting each word in query by using regular expression and then we will use toUppercase() and toLowerCase() methods of javascript and will capitalize every word of the query.

toTitleCase(str) {
    return str.replace(
        /\w\S*/g,
        function(txt) {
            return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
        }
    );
}

this.knowledgeservice.getSearchResults(this.toTitleCase(querypay.query))

 

Like this we now get results for almost all queries.

Limiting information in Knowledge Graph without terminating the sentences:

To solve this issue, we decided to show only four lines of data retrieved from Wikipedia API

For this we have implemented a getPosition function which will take three parameters a string, a substring and a index. Here string is the whole string from which the function will return the index of index position substring.

getPosition(string, subString, index) {
    return string.split(subString, index).join(subString).length;
 }
this.description = this.description.slice(0, this.getPosition(this.description, '.', 4) + 1);

 

Here, We get the index of 4th ‘.’ full stop by getPosition() function and then pass it to javascript slice method to slice the string upto that position.

Using this we limited results to 4 lines without terminating each line in middle.

References

1.W3Schools Regular Expression: https://www.w3schools.com/jsref/jsref_obj_regexp.asp

2.Javascript Slice method: https://www.w3schools.com/jsref/jsref_slice_array.asp

3.Wikipedia API: https://www.mediawiki.org/wiki/API:Main_page

Continue Reading

Displaying Charts on Lightbox

Susper displayed charts which shows graphical representation of Result Frequency and Protocol Distribution(http or https). But since results from these charts added little value it was decided to display charts on a click of a button on a lightbox. (Issue: https://github.com/fossasia/susper.com/issues/1066 ). In this blog,I will describe how I have implemented charts on lightbox.

Uniting the graphs(Line and Bar Graph) in a same class:

Before beginning the implementation of lightbox, we should unite all the components that will be displayed on lightbox in a single class.

Here I have put the different types of charts in a class named graph.

<div class="graph large"> <div class="linegraph"><canvas baseChart
           [datasets]="lineChartData"
           [labels]="lineChartLabels"
           [options]="lineChartOptions"
           [chartType]="lineChartType"
           [colors]="lineChartColors"
></canvas></div><div class="bargraph"><canvas baseChart
           [datasets]="barChartData"
           [labels]="barChartLabels"
           [options]="barChartOptions"
           [chartType]="barChartType"
           [colors]="lineChartColors"
   ></canvas></div></div>

Implementing button to toggle display of light box:

To show and hide lightbox along with charts we must have a button.

Clicking on which should trigger a function which will internally execute the logic in typescript file to show and hide the lightbox.

<button class="btn" id="toggle-chart-button" (click)="BoxToggle()">
           {{analyticsStatus}} Analytics
 </button>

Creating a translucent black background:

Now to have a black overlay for lightbox we need a div element and inside it we need a closing button.

Clicking on the black overlay should toggle status of light box therefore we have binded click event with BoxToggle() method.

<div id="fade" class="black_overlay" (click)='BoxToggle()'><a class="closeX" id="closeX">&#10006;</a></div>

Creating a white block to display charts:

Now after creating a black overlay in background we need a white foreground to display charts.

Therefore I have implemented a div element and put all the chart elements inside the div element so that it will be displayed on a white foreground on a translucent black background.

<div id="light" class="white_content">
<div class="graph large"><div class="linegraph"><canvas baseChart
           [datasets]="lineChartData"
           [labels]="lineChartLabels"
           [options]="lineChartOptions"
           [chartType]="lineChartType"
           [colors]="lineChartColors"
   ></canvas></div>
<div class="bargraph"><canvas baseChart
           [datasets]="barChartData"
           [labels]="barChartLabels"
           [options]="barChartOptions"
           [chartType]="barChartType"
           [colors]="lineChartColors"
   ></canvas></div></div></div>

CSS for all above elements:

All the CSS which is written for charts, toggle button, white block and black overlay

To make it look attractive and similar to market leader can be found here.

Implementing the logic in typescript file:

Now we have created all the elements in frontend to display the lightbox but we need a little logic to show and hide the lightbox on clicking the button. For this I have used a variable analyticsStatus which is a string and is initialised with value ‘Show Chart’. Its value is displayed on the toggle button. I have used this variable with BoxToggle() function for implementing the logic for showing and hiding the lightbox.

This function checks the status of message and toggles it along with toggling lightbox with some javascript code. The code for the function is here.

BoxToggle() { if (this.analyticsStatus === 'Show Chart') {
     this.analyticsStatus = 'Hide Chart';
     document.getElementById('light').style.display = 'block';
     document.getElementById('fade').style.display = 'block';
   } else {
     this.analyticsStatus = 'Show Chart';
     document.getElementById('light').style.display = 'none';
     document.getElementById('fade').style.display = 'none'; }}

Resources

1.Creating a lightbox: https://www.emanueleferonato.com/2007/08/22/create-a-lightbox-effect-only-with-css-no-javascript-needed/

2.W3School lightbox: https://www.w3schools.com/howto/howto_js_lightbox.asp

3.Angular 2 Lightbox: https://lokeshdhakar.com/projects/lightbox2/

Continue Reading

Creating new config to set Title of Loklak Webpages

For a Search Engine and Progressive Web Application (PWA) like Loklak, it is important to change the title of each web page with the appropriate query term or configuration of the current active page (A great title can result in higher click-throughs and more traffic to the site, and hence its followed by search engines like Google). For solving this problem, I am writing this blog post which creates a new configuration to change the title dynamically across the whole project by using just one Action thereby maximizing reusability of the code and minimizing duplication.

Create Action for title

We would need to create a new separate title action following the architecture in loklak.

Create a new title.ts file under Actions with the following code:

import { Action } from ‘@ngrx/store’;
export const ActionTypes = {
    SET_TITLE: ‘[Title] SET TITLE’
};
export class SetTitleAction implements Action {
    type = ActionTypes.SET_TITLE;
    constructor (public payload: string) { }
}
export type Actions
    = SetTitleAction;

Create reducer for title

For accessing the current title of the webpage, it is important to create a reducer function for the title following the pattern in loklak.

import * as titleAction from ‘../actions/title’;
export interface State {
	title: string;
}

export const initialState: State = {
	title: '',
};

export function reducer(state: State =
	initialState, action: titleAction.Actions):
	State {
	switch (action.type) {
		case titleAction.ActionTypes.SET_TITLE:
		{
			const titleObt: string =
				action.payload;
			return Object.assign({}, state,
			{
				title: titleObt
			});
		}
		default: {
			return state;
		}
	}
}
export const getTitle =
	(state: State) => state.title;

Create selector and add reducer function to root reducer

The title can be accessed from the current state from store when it would be added to the root state and reducer. Firstly import the title reducer created above and define the title property in root reducer and create a selector for the same.

import * as fromTitle from ‘./title’;
export interface State {
	
	title: fromTitle.State;
}

export const reducers: ActionReducerMap<State> = {
	
	title: fromTitle.reducer
};

export const getTitleState = (state: State) => state.title;

export const getTitle = createSelector(getTitleState,
   fromTitle.getTitle);

Create Effect to change actual title

Now comes the actual part which controls the changing of webpage title using ‘Title’ property of Platform-Browser. Create a new title.effects.ts file under effects using pattern in loklak.

import { Injectable } from ‘@angular/core’;
import { Title } from ‘@angular/platform-browser’;
import { Effect, Actions, ofType }
	from ‘@ngrx/effects’;
import { Observable } from ‘rxjs’;
import { map } from ‘rxjs/operators’;
import * as titleAction from ‘../actions/title’;

@Injectable()
export class SetTitleEffects {

	@Effect({ dispatch: false })
	resetTitle$: Observable<void> = this.actions$
		.pipe(
			ofType(
				titleAction.ActionTypes
					.SET_TITLE
			),
			map((action) => {
			const title =
		action[‘payload’];
			this.titleService
		.setTitle(title);
			})
		);

	constructor(
		private actions$: Actions,
		private titleService: Title
	) { }
}

 

Here the ‘payload’ actually contains the query searched by the user, which will be used update the title.

Note: Before adding a new Effect, the old Effect needs to be removed.

After creating the Effect, add it into app.module.ts and export it in the index.ts.

import {
	
	SetTitleEffects
} from ‘./effects’;

EffectsModule.forRoot([
	
	SetTitleEffects
])

How to set title using this configuration?

Now comes the interesting part. It becomes very easy to set title from any service or component by following the procedure given below.

  • Import the created title action.

import * as titleAction from ‘../actions/title’;

 

  • Set the required title by dispatching the SetTitleAction.
this.store.dispatch(new titleAction
	.SetTitleAction(‘Loklak Search’));

 

You’re all set now! The title has been changed to ‘Loklak Search’ which is what we expect.

Resources

Continue Reading

Dispatching a search action to get result for a query in Loklak Search

For a new comer in Loklak, it’s not really easy to understand the complete codebase to just get results for a query without explicitly using navigation route i.e. ‘/search’. In order to help newcomers to easily understand a simple way to get results for a query in the codebase, I am writing this blog post.

Setting up a query

A sample searchQuery will be created of type Query. And following discussion will provide a way to get the results for a sample query – ‘from:Fossasia’ (Last 30 days tweets from FOSSASIA).

searchQuery: Query = {
    displayString: ‘from:FOSSASIA,
    queryString: ‘from:FOSSASIA’,
    routerString: ‘from:FOSSASIA’,
    filter: { video: false, image: false },
    location: null,
    timeBound: { since: null, until: null },
    from: true
}

 

Process discussed here can be used to get results in any class file within Loklak Search.

Using necessary imports

First step would be to add all necessary imports.

import { OnInit } from ‘@angular/core’;
import { Store } from ‘@ngrx/store’;
import { Observable } from ‘rxjs/Observable’;
import * as fromRoot from ‘../../reducers’;
import { Query } from ‘../../models/query’;
import * as searchAction from ‘../../actions/api’;
import { ApiResponseResult } from ‘../../models/api-response’;

 

Note: No need to import OnInit, if the class already implements it (OnInit is one of the Angular’s Lifecycle Hooks which controls the changes when the component containing it gets loaded initially).

The Query is used to define the type of searchQuery variable created above. And ApiResponseResult is the type of result which will be obtained on searching the searchQuery.

Declaring response variables and store object

Next step would be to declare the variables with the type of response and create store object of type Store with the current State of the application.

public isSearching$: Observable<boolean>;
public apiResponseResults$: Observable<ApiResponseResult[]>;
constructor(
    private store: Store<fromRoot.State>
) { }

 

isSearching$ holds an Observable which can be subscribed to get a boolean value which states the current status of searching of query. apiResponseResults$ holds the actual response of query.

Dispatching a SearchAction

The most crucial part comes here to dispatch a SearchAction with the created searchQuery as payload. A new method would be created with void as return type to dispatch the SearchAction.

private queryFromURL(): void {
    this.store.dispatch(
        new searchAction.SearchAction(this.searchQuery)
    );
}

Selecting and storing results from store

A new method would be created to store the current searching status along with the response of input query in isSearching$ and apiResponseResults$ respectively. Now the selector for the reducer function corresponding to the search and api response entities would be called.

private getDataFromStore(): void {
    this.isSearching$ = 
        this.store.select(fromRoot.getSearchLoading);
    this.apiResponseResults$ =   
        this.store.select(
            fromRoot.getApiResponseEntities
    );
}

Dispatching and Selecting on view Init

create ngOnInit() method, If you have not already done and call the respective dispatching and select method inside it respectively.

ngOnInit() {
    this.queryFromURL();
    this.getDataFromStore();
}

How should one test the results?

Call these lines inside any method and look for the results on browser console window.

console.log(this.isSearching$);
console.log(this.apiResponseResults$);

Resources

Continue Reading

Adding Speech Component in Loklak Search

Speech recognition service for voice search is already embedded in Loklak. Now the idea is to use this service and create a new separate component for voice recognition with an interactive and user friendly interface. This blog will cover every single portion of an Angular’s redux based component from writing actions and reducers to the use of created component in other required components.

Creating Action and Reducer Function

The main idea to create an Action is to control the flow of use of Speech Component in Loklak Search. The Speech Component will be called on and off based on this Action.

Here, the first step is to create speech.ts file in actions folder with the following code:

import { Action } from '@ngrx/store';

export const ActionTypes = {
   MODE_CHANGE: '[Speech] Change',
};

export class SearchAction implements Action {
   type = ActionTypes.MODE_CHANGE;

   constructor(public payload: any) {}
}

export type Actions
   = SearchAction;

 

In the above segment, only one action (MODE_CHANGE) has been created which is like a boolean value which is being returned as true or false i.e. whether the speech component is currently in use or not. This is a basic format to be followed in creating an Action which is being followed in Loklak Search. The next step would be to create speech.ts file in reducers folder with the following code:

import { Action } from '@ngrx/store';
import * as speech from '../actions/speech';
export const MODE_CHANGE = 'MODE_CHANGE';

export interface State {
   speechStatus: boolean;
}
export const initialState: State = {
   speechStatus: false
};
export function reducer(state: State = initialState,
   action: speech.Actions): State {
        switch (action.type) {
            case speech.ActionTypes.MODE_CHANGE: {
                const response = action.payload;
                return Object.assign({}, state,
                {speechStatus: response});
       }
       default: {
           return state;
       }
   }
}
export const getspeechStatus = (state: State) =>
    state.speechStatus;

 

It follows the format of reducer functions created in Loklak Search. Here, the main key point is the state creation and type of value it is storing i.e. State is containing a speechStatus of type boolean. Defining an initial state with speechStatus value false (Considering initially the Speech Component will not be in use). The reducer function a new state by toggling the input state based on the type of Action created above and it returns the input state by default. At last wrapping the state as a function and returning the state’s speechStatus value.

Third and last step in this section would be to create a selector for the above reducer function in the root reducer index file.

Import and add speech from speech reducer file into the general state in root reducer file. And at last export the created selector function for speech reducer.

import * as fromSpeech from './speech';
export interface State {
   ...
   speech: fromSpeech.State;
}
export const getSpeechState = (state: State) =>
    state.speech;
export const getspeechStatus = createSelector(
    getSpeechState, fromSpeech.getspeechStatus);

Creating Speech Component

Now comes the main part to create and define the functioning of Speech Component. For creating the basic Speech Component, following command is used:

ng generate component app/speech --module=app

 

It will automatically create and provide Speech Component in app.module.ts. The working structure of Speech Component has been followed as of Google’s voice recognition feature for voice searching. Rather than providing description of each single line of code the following portion will cover the main code responsible for the functioning of Speech Component.

Importing and defining speech service in constructor:

import {
    SpeechService
} from '../services/speech.service';
constructor(
       private speech: SpeechService,
       private store: Store<fromRoot.State>,
       private router: Router
   ) {
       this.resultspage =this.router.url
       .toString().includes('/search');
       if (this.resultspage) {
           this.shadowleft = '-103px';
           this.shadowtop = '-102px';
       }
       this.speechRecognition();
}
speechRecognition() {
    this.speech.record('en_US').subscribe(voice =>
        this.onquery(voice));
}

 

When the Speech Component is called, speechRecognition() method will start recording speech (It will use the record() method from speech service to record the user voice).

For fluctuating border height and color of voice search icon, a resettimer() method is created.

randomize(min, max) {
    let x;
    x = (Math.random() * (max - min) + min);
    return x;
}
resettimer(recheck: boolean = false) {
    this.subscription.unsubscribe();
    this.timer = Observable.timer(0, 100);
    this.subscription = this.timer.subscribe(t => {
    this.ticks = t;
        if (t % 10 === 0 && t <= 20) {
            this.buttoncolor = '#f44';
            this.miccolor = '#fff';
            this.borderheight =
                this.randomize(0.7, 1);
            if (this.resultspage) {
                this.borderheight =
                    this.randomize(0.35, 0.5);
            }
            if (!recheck) {
                this.resettimer(true);
            }
        }
        if (t === 20) {
            this.borderheight = 0;
        }
        if (t === 30) {
            this.subscription.unsubscribe();
            this.store.dispatch(new speechactions
            .SearchAction(false));
        }
    });
}

 

The randomize() method provides a random number between min and max value.

To put on check and display status as message on things like whether microphone is working, or user has spoken something, or if the speech is being recorded, based on the time elapsed in calling of speech component and actual voice recording, the following portion of code is written in ngOnInit() method.

ngOnInit() {
    this.timer = Observable.timer(1500, 2000);
    this.subscription = this.timer.subscribe(t => {
        this.ticks = t;
        if (t === 1) {
            this.message = 'Listening...';
        }
        if (t === 4) {
            this.message = 'Please check your 
            microphone and volume levels.';
            this.miccolor = '#C2C2C2';
        }
        if (t === 6) {
            this.subscription.unsubscribe();
            this.store.dispatch(new speechactions
                .SearchAction(false));
        }
    });
}

 

The logic can be understood as if the elapsed time is 1 sec, it means it is listening to the speaker’s voice. And if the elapsed time is 4 sec, it means there is something wrong and user will be asked to check for the microphone and volume levels. At last if it tends to 6 seconds, then the Speech Component will be called off with the dispatched Action as false which is defined above (That means it is no longer in use).

Embed Speech Component in main App Component

Now comes the last part to use the created featured component in the required place. Code below describes embedding Speech Component in App Component.

Import SpeechService and required modules.

import {
    SpeechService
} from './services/speech.service';
import { Observable } from 'rxjs/Observable';

 

hidespeech will be used to store the current status of Speech Component (whether its in use or not), and completeQuery$ and searchData store the voice recorded in form Observable and String. completeQuery$ is optional (If the Speech Component is unable to track voice of speaker by any means, then it will not contain any value and hence searchData will be empty).

hidespeech: Observable<any>;
completeQuery$: Observable<any>;
searchData: String;

 

Creating speech parameter in constructor and store the current status of speech and store it into hidespeech. Based on the subscribed value of hidespeech, speech service’s stoprecord() will be called (To stop recording when the speech recognition completes). After recording stops, store the whole query in completeQuery$.

constructor (
    private speech: SpeechService
) {
    this.hidespeech = store.select(
        fromRoot.getspeechStatus);
    this.hidespeech.subscribe(hidespeech => {
        if (!hidespeech) {
            this.speech.stoprecord();
        }
    });
    this.completeQuery$ = store.select(
        fromRoot.getQuery);
    this.completeQuery$.subscribe(data => {
        this.searchData = data;
    });
}

 

Add the Speech Component in app.component.html. Now the main logic of calling Speech Component will be based on the subscribed observable value of hidespeech (If false then call Speech Component else not).

<app-speech *ngIf="hidespeech|async"></app-speech>

Using Speech Component in Home and FeedHeader Component

Import Speech Service and speech Action created above, and create hidespeech to store the current status of Speech Component.

import * as speechactions from '../../actions/speech';
import {
    SpeechService
} from '../../services/speech.service';
hidespeech: Observable<boolean>;

 

Create speech parameter of type SpeechService and store the current status of Speech Component in hidespeech. Dispatch speechactions.SearchAction (payload as true) for inferring that the Speech Component is currently in use.

constructor(
    private speech: SpeechService
) {
    this.hidespeech = store
        .select(fromRoot.getspeechStatus);
}
speechRecognition() {
    this.store.dispatch(
        new speechactions.SearchAction(true));
}

How to use the Speech Component?

Goto Loklak and click on Voice Input Icon. It will popup a screen as below.

Now, speak something to search. E.g. Google, the screen will turn into something like below with the spelled value displayed on screen.

If something goes wrong (Microphone did not work, low volume levels or unrecognisable voice), then screen will show something like:

On successful recognition of speech, the query will be set and the results will be shown as

Similar process is being followed on results page to make a search query using voice.

Resources

Continue Reading

Adding ‘Voice Search’ Feature in Loklak Search

It is beneficial to have a voice search feature embedded in a search engine like loklak.org based on PWA (Progressive Web Application) architecture. This will allow users to make query using voice on phone or computer. For integrating voice search, JavaScript Web Speech API (also known as webkitSpeechRecognition) is used which allows us to add speech recognition feature in any website or a web application.

Integration Process

For using webkitSpeechRecognition, a separate typescript service is being created along with its relevant unit test

  1. speech.service.ts
  2. speech.service.spec.ts

The main idea to implement the voice search was

  1. To create an injectable speech service.
  2. Write methods for starting and stopping voice record in it.
  3. Import and inject the created speech service into root app module.
  4. Use the created speech service into required HomeComponent and FeedHeaderComponent.

Structure of Speech Service

The speech service mainly consists of two methods

  • record() method which accepts lang as string input which specifies the language code for speech recording and returns a string Observable.

   record(lang: string): Observable<string> {
           return Observable.create(observe => {
                 const webkitSpeechRecognition }: IWindow = 
                        <IWindow>window;
                 this.recognition = new webkitSpeechRecognition();
                 this.recognition.continuous = true;
                 this.recognition.interimResults = true;
                 this.recognition.onresult = take => 
                 this.zone.run(()                            
                 observe.next(take
                 .results.item
                 (take.results.
                 length - 1).
                 item(0).
                 transcript);
                 this.recognition
                 .onerror = 
                 err => observe
                 .error(err);
                 this.recognition.onend 
                 = () => observe
                 .complete();
                 this.recognition.lang = 
                 lang;
                 this.recognition.start( 
             );
        });
     }

 

Using observe.complete() allows speech recognition action to stop when the user stops speaking.

  • stoprecord() method which is used to stop the current instance of recording.

 stoprecord() {
       if (this.recognition) {
           this.recognition.stop();
       }
   }

 

stop() method is used to stop the current instance of speech recognition.

Using Speech Service in required Component

In Loklak Search, the speech service have been included in two main components i.e. HomeComponent and FeedHeaderComponent. The basic idea of using the created speech service in these components is same.

In the TypeScript (not spec.ts) file of the two components, firstly import the SpeechService and create its object in constructor.

constructor( private speech: SpeechService ) { }

 

Secondly, define a speechRecognition() method and use the created instance or object of SpeechService in it to record speech and set the query as the recorded speech input. Here, default language has been set up as ‘en_US’ i.e. English.

 stoprecord() {
       if (this.recognition) {
           this.recognition.stop();
       }
   }

 

After user clicks on speech icon, this method will be called and the recorded speech will be set as the query and there will be router navigation to the /search page where the result will be displayed.

Resources

Continue Reading

Implementing Direct URL in loklak Media Wall

Direct URL is a web address which redirects the user to the preset customized media wall so that the media wall can directly be used to be displayed on the screen. Loklak media wall provides direct URL which has information related to customizations set by the user included in the web address. These customizations, as the query parameters are detected when the page is initialized and actions are dispatched to make changes in the state properties, and hence, the UI properties and the expected behaviour of media wall.

In this blog, I would be explaining how I implemented direct URL in loklak media wall and how customizations are detected to build on initialization of component, a customized media wall.

Flow Chart

Working

Media Wall Direct URL effect

This effect detects when the WALL_GENERATE_DIRECT_URL action is dispatched and creates a direct URL string from all the customization state properties and dispatches a side action WallShortenDirectUrlAction() and stores direct URL string as a state property. For this, we need to get individual wall customization state properties and create an object for it and supply it as a parameter to the generateDirectUrl() function. Direct URL string is returned from the function and now, the action is dispatched to store this string as a state property.

@Effect()
generateDirectUrl$: Observable<Action>
= this.actions$
.ofType(mediaWallDirectUrlAction.ActionTypes.WALL_GENERATE_DIRECT_URL)
.withLatestFrom(this.store$)
.map(([action, state]) => {
return {
query: state.mediaWallQuery.query,
.
.
.
wallBackground: state.mediaWallCustom.wallBackground
};
})
.map(queryObject => {
const configSet = {
queryString: queryObject.query.displayString,
.
.
.
wallBackgroundColor: queryObject.wallBackground.backgroundColor
}
const shortenedUrl = generateDirectUrl(configSet);
return new mediaWallDirectUrlAction.WallShortenDirectUrlAction(shortenedUrl);
});

Generate Direct URL function

This function generates Direct URL string from all the current customization options value. Now,  keys of the object are separated out and for each element of the object, it checks if there is some current value for the elements and it then first parses the value of the element into URI format and then, adds it to the direct URL string. In such a way, we are creating a direct URL string with these customizations provided as the query parameters.

export function generateDirectUrl(customization: any): string {
const shortenedUrl = ;const activeFilterArray: string[] = new Array<string>();
let qs = ;
Object.keys(customization).forEach(config => {
if (customization[config] !== undefined && customization[config] !== null) {
if (config !== ‘blockedUser’ && config !== ‘hiddenFeedId’) {
qs += `${config}=${encodeURIComponent(customization[config])}&`;
}
else {
if (customization[config].length > 0) {
qs += `${config}= ${encodeURIComponent(customization[config].join(‘,’))}&`;
}
}
}
});
qs += `ref=share`;
return qs;
}

Creating a customized media wall

Whenever the user searches for the URL link on the web, a customized media wall must be created on initialization. The media wall component detects and subscribes to the URL query parameters using the queryParams API of the ActivatedRoute. Now, the values are parsed to a required format of payload and the respective actions are dispatched according to the value of the parameters. Now, when all the actions are dispatched, state properties changes accordingly. This creates a unidirectional flow of the state properties from the URL parameters to the template. Now, the state properties that are supplied to the template are detected and a customized media wall is created.

private queryFromURL(): void {
this.__subscriptions__.push(
this.route.queryParams
.subscribe((params: Params) => {
const config = {
queryString: params[‘queryString’] || ,
imageFilter: params[‘imageFilter’] || ,
profanityCheck: params[‘profanityCheck’] || ,
removeDuplicate: params[‘removeDuplicate’] || ,
wallHeaderBackgroundColor: params[‘wallHeaderBackgroundColor’] || ,
wallCardBackgroundColor: params[‘wallCardBackgroundColor’] || ,
wallBackgroundColor: params[‘wallBackgroundColor’] ||
}
this.setConfig(config);
})
);
}public setConfig(configSet: any) {
if (configSet[‘displayHeader’]) {
const isTrueSet = (configSet[‘displayHeader’] === ‘true’);
this.store.dispatch(new mediaWallDesignAction.WallDisplayHeaderAction(isTrueSet));
}
.
.
if (configSet[‘queryString’] || configSet[‘imageFilter’] || configSet[‘location’]) {
if (configSet[‘location’] === ‘null’) {
configSet[‘location’] = null;
}
const isTrueSet = (configSet[‘imageFilter’] === ‘true’);
const query = {
displayString: configSet[‘queryString’],
queryString: ,
routerString: configSet[‘queryString’],
filter: {
video: false,
image: isTrueSet
},
location: configSet[‘location’],
timeBound: {
since: null,
until: null
},
from: false
}
this.store.dispatch(new mediaWallAction.WallQueryChangeAction(query));
}
}

Now, the state properties are rendered accordingly and a customized media wall is created. This saves a lot of effort by the user to change the customization options whenever uses the loklak media wall.

Reference

Continue Reading

Hiding the Scrollbar in HTML in the loklak Media Wall

Loklak media wall needs to provide an appealing view to the user. The issue of visibility of scrollbars appeared on the browsers which uses webkit as a browser engine, for example, Google chrome and some others. This issue caused problems like shifting of elements when scrollbars are visible, bad UI. The task was to hide the scrollbars completely from the application while still making overflow of web page resources available by scrolling.

In this blog, I explain how to hide scrollbars from a webpage and still making scrolling available for the user.

Procedure

 

  • Removing scrollbars from the body division: By default, the <body> tag has the style property overflow:auto which makes overflow available automatically if web page resources exceed the current page dimensions. Therefore, to hide scrollbar from the body, we need to hide the overflow using overflow:hidden.

body {
overflow: hidden;
}

 

  • Creating a child wrapper division for the body: Since body now doesn’t provide scrolling available for the web page, we need to create a wrapper division for the web page for which scrolling would be available, however, scrollbars would not be visible. The idea behind this step is that wrapper division would now act as a wrapper for the web page and if there is some overflow of web page resources, scrolling can be performed from the child division (for which scrollbar can be hidden) instead of parent division.

 

The wrapper division needs to be a block which should occupy total web page available and CSS property overflow should be set to auto.

div.wrapper {
display: block;
width: 100%;
height: 100%;
overflow: auto;
}

 

  • Hiding scrollbars from the wrapper division: The browsers which uses webkit as the browser engines provides scrollbar to every DOM element as webkit-scrollbar which can be customized according to our need. We can now turn scrollbar background to transparent or either set width to 0. Now, since problem of shifting of DOM elements exists, we can need to set width to 0.

.wrapper::-webkitscrollbar {
width: 0px;
}

 

  • Blocking Scroll Blocks: For Angular Material Dialog box, same problem exists since Scroll blocks sets the CSS property of HTML to scroll. This causes the whole  html element to have a scroll. For the same, we can set overflow to hidden by using \deep\ tag to change CSS property deeply of different component of Angular project.

/deep/ html.cdkglobalscrollblock {
overflow: hidden;
}

References

Continue Reading

Feeds Moderation in loklak Media Wall

Loklak Media Wall provides client side filters for entities received from loklak status.json API like blocking feeds from a particular user, removing duplicate feeds, hiding a particular feed post for moderating feeds. To implement it, we need pure functions which remove the requested type of feeds and returns a new array of feeds. Moreover, the original set of data must also be stored in an array so that if filters are removed, the requested data is provided to the user

In this blog, I would be explaining how I implemented client side filters to filter out a particular type of feeds and provide the user with a cleaner data as requested.

Types of filters

There are four client-side filters currently provided by Loklak media wall:

    • Profanity Filter: Checks for the feeds that might be offensive and removes it.
    • Remove Duplicate: Removes duplicate feeds and the retweets from the original feeds
    • Hide Feed: Removes a particular feed from the feeds
    • Block User: Blocks a User and removes all the feeds from the particular user

It is also important to ensure that on pagination, new feeds are filtered out based on the previous user requested moderation.

Flow Chart

The flow chart explains how different entities received from the server is filtered and how original set of entities is maintained so that if the user removes the filter, the original filtered entities are recovered.

Working

Profanity Filter

To avoid any obscene language used in the feed status to be shown up on media wall and providing a rather clean data, profanity filter can be used. For this filter, loklak search.json APIs provide a field classifier_profanity which states if there is some swear word is used in the status. We can check for the value of this field and filter out the feed accordingly.

export function profanityFilter(feeds: ApiResponseResult[]): ApiResponseResult[] {
const filteredFeeds: ApiResponseResult[] = [];
feeds.forEach((feed) => {
if ( feed.classifier_language !== null && feed.classifier_profanity !== undefined ) {
if (feed.classifier_profanity !== ‘sex’ &&  feed.classifier_profanity !== ‘swear’) {
filteredFeeds.push(feed);
}
}
else {
filteredFeeds.push(feed);
}
});
return filteredFeeds || feeds;
}

Here, we check if the classifier_profanity field is either not ‘swear’ or ‘sex’ which clearly classifies the feeds and we can push the status accordingly. Moreover, if no classifier_profanity field is provided for a particular field, we can push the feed in the filtered feeds.

Remove Duplicate

Remove duplicate filter removes the tweets that are either retweets or even copy of some feed and return just one original feed. We need to compare field id_str which is the status id of the feed and remove the duplicate feeds. For this filter, we need to create a map and compare feeds on map object and remove the duplicate feeds iteratively and return the array of feeds with unique elements.

export function removeDuplicateCheck(feeds: ApiResponseResult[]): ApiResponseResult[] {
const map = { };
const filteredFeeds: ApiResponseResult[] = [];
const newFeeds: ApiResponseResult[] = feeds;
let v: string;
for (let a = 0; a < feeds.length; a++) {
v = feeds[a].id_str;
if (!map[v]) {
filteredFeeds.push(feeds[a]);
map[v] = true;
}
}
return filteredFeeds;
}

Hide Feed

Hide Feed filter can be used to hide a particular feed from showing up on media wall. It can be a great option for the user to hide some particular feed that user might not want to see. Basically, when the user selects a particular feed, an action is dispatched with payload being the status id i.e. id_str. Now, we pass feeds and status id through a function which returns the particular feed. All the feeds with the same id_str are also removed from the feeds array.

export function hideFeed(feeds: ApiResponseResult[], statusId: string ): ApiResponseResult[] {
const filteredFeeds: ApiResponseResult[] = [];
feeds.forEach((feed) => {
if (feed.id_str !== statusId) {
filteredFeeds.push(feed);
}
});
return filteredFeeds || feeds;
}

User can undo the action and let the filtered feed again show up on media wall. Now, for implementing this, we need to pass original entities, filtered entities and the id_str of the particular entity through a function which checks for the particular entity with the same id_str and add it in the filtered entities and return the new array of filtered entities.

export function showFeed(originalFeeds: ApiResponseResult[], feeds: ApiResponseResult[], statusId: string ): ApiResponseResult[] {
const newFeeds = […feeds];
originalFeeds.forEach((feed) => {
if (feed.id_str === statusId) {
newFeeds.push(feed);
}
});
return newFeeds;
}

Block User

Block User filter can be used blocking feeds from a particular user/account from showing up on media wall. To implement this, we need to check for the User ID field user_id of the user and remove all the feeds from the same User ID. The function accountExclusion takes feeds and user_id (of the accounts) as a parameter and returns an array of filtered feeds removing all the feeds of the requested users/accounts.

export function accountExclusion(feeds: ApiResponseResult[], userId: string[] ): ApiResponseResult[] {
const filteredFeeds: ApiResponseResult[] = [];
let flag: boolean;
feeds.forEach((feed) => {
flag = false;
userId.forEach((user) => {
if (feed.user.user_id === user) {
flag = true;
}
});
if (!flag) {
filteredFeeds.push(feed);
}
});return filteredFeeds || feeds;
}

Key points

It is important to ensure that the new feeds (received on pagination or on a new query) must also be filtered according to the user requested filter. Therefore, before storing feeds in a state and supplying to templates after pagination, it must be ensured that new entities are also filtered out. For this, we need to keep boolean variables as a state property which checks if a particular filter is requested by a user and applies the filter to the new feeds accordingly and store filtered feeds in the filteredFeeds accordingly.

Also, the original feeds must be stored separately so that on removing filters the original feeds are regained. Here, entities stores the original entities received from the server.

case apiAction.ActionTypes.WALL_SEARCH_COMPLETE_SUCCESS: {
const apiResponse = action.payload;
let newFeeds = accountExclusion(apiResponse.statuses, state.blockedUser);
if (state.profanityCheck) {
newFeeds = profanityFilter(newFeeds);
}
if (state.removeDuplicate) {
newFeeds = removeDuplicateCheck(newFeeds);
}return Object.assign({}, state, {
entities: apiResponse.statuses,
filteredEntities: newFeeds,
lastResponseLength: apiResponse.statuses.length
});
}

case wallPaginationAction.ActionTypes.WALL_PAGINATION_COMPLETE_SUCCESS: {
const apiResponse = action.payload;
let newFeeds = accountExclusion(apiResponse.statuses, state.blockedUser);
if (state.profanityCheck) {
newFeeds = profanityFilter(apiResponse.statuses);
}
let filteredEntities = […newFeeds, state.filteredEntities];
if (state.removeDuplicate) {
filteredEntities = removeDuplicateCheck(filteredEntities);
}

return Object.assign({}, state, {
entities: [ apiResponse.statuses, state.entities ],
filteredEntities
});
}

Reference

Continue Reading

Creating A Better Responsive Design In Susper

A lot of work has been done on making Susper, a wonderful search-engine and still more work have to be done on it. To become a good competitor in the market, one should make their website UI design such that:

  • It should be eye-catching for the users on the first-time visit to the website.
  • It should be easy to use with simple UI features rather than having more complex UI features.

We have been more oriented towards the material design. We have used Bootstrap technology for designing UI. Earlier, we proposed an idea of creating a UI using Angular Material v2 but it was dropped due to time limitations and other issue priorities.

To make Susper a better competitor in the market, we made sure it should be responsive as well on the following devices:

  • Mobile screen devices:
    • 320px – Smaller screen size.
    • 375px – Medium screen size.
    • 425px – Larger screen size.
  • Tablets:
    • 768px – default screen size for tablets.
  • Laptops:
    • 1024px – Smaller screen size.
    • 1440px – Larger screen size.
  • 4K:
    • 2560px – Default screen size.

We targeted these devices using @media queries in CSS3. For e.g. if I want to make a site responsive for the mobile devices, I will be using:

@media screen and (minwidth: 320px) and (maxwidth: 425px) {
  // do something
}

 

Here, min-width: 320px means that the screen size should be greater than and equal to 320px and max-width: 425px means that the screen size should be less than and equal to 425px.

It is not necessary to use only these dimensions. Suppose if there is break in UI design between 320px and 425px then, one can add that screen size using @media query. In this case, nested @media queries play a quite good role.

@media screen and (minwidth: 320px) and (maxwidth: 425px) {
  // do something
  // let’s say, break in UI design is observed at 375px
  // add nested @media query
  @media screen and (minwidth: 375px) {
    // do something
  }
}

 

We’re still improving our CSS code at present following this grid pattern. One can check UI code at Susper repository hosted on GitHub: https://github.com/fossasia/susper.com

We have also used a lot of breakpoints which are not nested. But it’s good practice to break points in nested form. This will be solved while improving our CSS code.

Here are some screenshots of the current responsiveness of Susper:

  • Mobile screen devices:
  • Tablet devices:

  • Laptops:
  • 4K display:

Resources:

Continue Reading
Close Menu